• 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 cluster from 'cluster';
17import fs from 'fs';
18import path from 'path';
19import ts from 'typescript';
20import os from 'os';
21import sourceMap from 'source-map';
22
23import {
24  DEBUG,
25  ESMODULE,
26  EXTNAME_ETS,
27  EXTNAME_JS,
28  EXTNAME_TS,
29  EXTNAME_JSON,
30  EXTNAME_CJS,
31  EXTNAME_MJS,
32  TEMPORARY
33} from './common/ark_define';
34import {
35  nodeLargeOrEqualTargetVersion,
36  genTemporaryPath,
37  mkdirsSync,
38  validateFilePathLength,
39  toUnixPath,
40  isPackageModulesFile,
41  isFileInProject
42} from '../../utils';
43import {
44  tryMangleFileName,
45  writeObfuscatedSourceCode,
46} from '../../ark_utils';
47import { AOT_FULL, AOT_PARTIAL, AOT_TYPE } from '../../pre_define';
48import { SourceMapGenerator } from './generate_sourcemap';
49import {
50  handleObfuscatedFilePath,
51  enableObfuscateFileName,
52  enableObfuscatedFilePathConfig,
53  getRelativeSourcePath
54} from './common/ob_config_resolver';
55import {
56  CompileEvent,
57  createAndStartEvent,
58  stopEvent
59} from '../../performance';
60import { BytecodeObfuscator } from './bytecode_obfuscator';
61
62export let hasTsNoCheckOrTsIgnoreFiles: string[] = [];
63export let compilingEtsOrTsFiles: string[] = [];
64
65export function cleanUpFilesList(): void {
66  hasTsNoCheckOrTsIgnoreFiles = [];
67  compilingEtsOrTsFiles = [];
68}
69
70export function needAotCompiler(projectConfig: Object): boolean {
71  return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL ||
72    projectConfig.anBuildMode === AOT_PARTIAL);
73}
74
75export function isAotMode(projectConfig: Object): boolean {
76  return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL ||
77    projectConfig.anBuildMode === AOT_PARTIAL || projectConfig.anBuildMode === AOT_TYPE);
78}
79
80export function isDebug(projectConfig: Object): boolean {
81  return projectConfig.buildMode.toLowerCase() === DEBUG;
82}
83
84export function isBranchElimination(projectConfig: Object): boolean {
85  return projectConfig.branchElimination;
86}
87
88export function isMasterOrPrimary() {
89  return ((nodeLargeOrEqualTargetVersion(16) && cluster.isPrimary) ||
90    (!nodeLargeOrEqualTargetVersion(16) && cluster.isMaster));
91}
92
93export function changeFileExtension(file: string, targetExt: string, originExt = ''): string {
94  let currentExt = originExt.length === 0 ? path.extname(file) : originExt;
95  let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt));
96  return fileWithoutExt + targetExt;
97}
98
99export function removeCacheFile(cacheFilePath: string, ext: string): void {
100  let filePath = toUnixPath(changeFileExtension(cacheFilePath, ext));
101  if (fs.existsSync(filePath)) {
102    fs.rmSync(filePath);
103  }
104}
105
106export function shouldETSOrTSFileTransformToJS(filePath: string, projectConfig: Object, metaInfo?: Object): boolean {
107  let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath,
108    projectConfig, metaInfo);
109
110  if (!projectConfig.processTs) {
111    removeCacheFile(cacheFilePath, EXTNAME_TS);
112    return true;
113  }
114
115  if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) {
116    // file involves in compilation
117    const hasTsNoCheckOrTsIgnore = hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1;
118    // Remove cacheFile whose extension is different the target file
119    removeCacheFile(cacheFilePath, hasTsNoCheckOrTsIgnore ? EXTNAME_TS : EXTNAME_JS);
120    return hasTsNoCheckOrTsIgnore;
121  }
122
123  cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig);
124  cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS));
125  return fs.existsSync(cacheFilePath);
126}
127
128// This API is used exclusively to determine whether a file needs to be converted into a JS file without removing the cached files,
129// which differs from API 'shouldETSOrTSFileTransformToJS'.
130export function shouldETSOrTSFileTransformToJSWithoutRemove(filePath: string, projectConfig: Object, metaInfo?: Object): boolean {
131  if (!projectConfig.processTs) {
132    return true;
133  }
134
135  if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) {
136    // file involves in compilation
137    return hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1;
138  }
139
140  let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath,
141    projectConfig, metaInfo);
142  cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig);
143  cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS));
144  return fs.existsSync(cacheFilePath);
145}
146
147function updateCacheFilePathIfEnableObuscatedFilePath(filePath: string, cacheFilePath: string, projectConfig: Object): string {
148  const isPackageModules = isPackageModulesFile(filePath, projectConfig);
149  if (enableObfuscatedFilePathConfig(isPackageModules, projectConfig) && enableObfuscateFileName(isPackageModules, projectConfig)) {
150    return handleObfuscatedFilePath(cacheFilePath, isPackageModules, projectConfig);
151  }
152  return cacheFilePath;
153}
154
155export async function writeFileContentToTempDir(id: string, content: string, projectConfig: Object,
156  logger: Object, parentEvent: CompileEvent, metaInfo: Object): Promise<void> {
157  if (isCommonJsPluginVirtualFile(id)) {
158    return;
159  }
160
161  if (!isCurrentProjectFiles(id, projectConfig)) {
162    return;
163  }
164
165  let filePath: string;
166  if (projectConfig.compileHar) {
167    // compileShared: compile shared har of project
168    filePath = genTemporaryPath(id,
169      projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath,
170      projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath,
171      projectConfig, metaInfo, projectConfig.compileShared);
172  } else {
173    filePath = genTemporaryPath(id, projectConfig.projectPath, projectConfig.cachePath, projectConfig, metaInfo);
174  }
175
176  const eventWriteFileContent = createAndStartEvent(parentEvent, 'write file content');
177  switch (path.extname(id)) {
178    case EXTNAME_ETS:
179    case EXTNAME_TS:
180    case EXTNAME_JS:
181    case EXTNAME_MJS:
182    case EXTNAME_CJS:
183      await writeFileContent(id, filePath, content, projectConfig, logger, metaInfo);
184      break;
185    case EXTNAME_JSON:
186      const newFilePath: string = tryMangleFileName(filePath, projectConfig, id, projectConfig.isBytecodeObfEnabled);
187      mkdirsSync(path.dirname(newFilePath));
188      fs.writeFileSync(newFilePath, content ?? '');
189      break;
190    default:
191      break;
192  }
193  stopEvent(eventWriteFileContent);
194}
195
196async function writeFileContent(sourceFilePath: string, filePath: string, content: string,
197  projectConfig: Object, logger: Object, metaInfo?: Object): Promise<void> {
198  if (!isSpecifiedExt(sourceFilePath, EXTNAME_JS)) {
199    filePath = changeFileExtension(filePath, EXTNAME_JS);
200  }
201
202  if (!isDebug(projectConfig)) {
203    const relativeSourceFilePath: string = getRelativeSourcePath(sourceFilePath, projectConfig.projectRootPath,
204      metaInfo?.belongProjectPath);
205    await writeObfuscatedSourceCode({content: content, buildFilePath: filePath,
206      relativeSourceFilePath: relativeSourceFilePath, originSourceFilePath: sourceFilePath, rollupModuleId: sourceFilePath},
207      logger, projectConfig, SourceMapGenerator.getInstance().getSourceMaps());
208    return;
209  }
210  mkdirsSync(path.dirname(filePath));
211  fs.writeFileSync(filePath, content, 'utf-8');
212}
213
214export function getEs2abcFileThreadNumber(): number {
215  const fileThreads: number = os.cpus().length < 16 ? os.cpus().length : 16;
216  return fileThreads;
217}
218
219export function isCommonJsPluginVirtualFile(filePath: string): boolean {
220  // rollup uses commonjs plugin to handle commonjs files,
221  // which will automatic generate files like 'jsfile.js?commonjs-exports'
222  return filePath.includes('\x00');
223}
224
225export function isCurrentProjectFiles(filePath: string, projectConfig: Object): boolean {
226  if (projectConfig.rootPathSet) {
227    for (const projectRootPath of projectConfig.rootPathSet) {
228      if (isFileInProject(filePath, projectRootPath)) {
229        return true;
230      }
231    }
232    return false;
233  }
234  return isFileInProject(filePath, projectConfig.projectRootPath);
235}
236
237export function genTemporaryModuleCacheDirectoryForBundle(projectConfig: Object): string {
238  const buildDirArr: string[] = projectConfig.aceModuleBuild.split(path.sep);
239  const abilityDir: string = buildDirArr[buildDirArr.length - 1];
240  const temporaryModuleCacheDirPath: string = path.join(projectConfig.cachePath, TEMPORARY, abilityDir);
241  mkdirsSync(temporaryModuleCacheDirPath);
242
243  return temporaryModuleCacheDirPath;
244}
245
246export function isSpecifiedExt(filePath: string, fileExtendName: string) {
247  return path.extname(filePath) === fileExtendName;
248}
249
250export function genCachePath(tailName: string, projectConfig: Object, logger: Object): string {
251  const pathName: string = projectConfig.cachePath !== undefined ?
252    path.join(projectConfig.cachePath, TEMPORARY, tailName) : path.join(projectConfig.aceModuleBuild, tailName);
253  mkdirsSync(path.dirname(pathName));
254
255  validateFilePathLength(pathName, logger);
256  return pathName;
257}
258
259export function isTsOrEtsSourceFile(file: string): boolean {
260  return /(?<!\.d)\.[e]?ts$/.test(file);
261}
262
263export function isJsSourceFile(file: string): boolean {
264  return /\.[cm]?js$/.test(file);
265}
266
267export function isJsonSourceFile(file: string): boolean {
268  return /\.json$/.test(file);
269}
270
271export async function updateSourceMap(originMap: sourceMap.RawSourceMap, newMap: sourceMap.RawSourceMap): Promise<any> {
272  if (!originMap) {
273    return newMap;
274  }
275  if (!newMap) {
276    return originMap;
277  }
278  const originConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(originMap);
279  const newConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(newMap);
280  const newMappingList: sourceMap.MappingItem[] = [];
281  newConsumer.eachMapping((mapping: sourceMap.MappingItem) => {
282    if (mapping.originalLine == null) {
283      return;
284    }
285    const originalPos =
286      originConsumer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn });
287    if (originalPos.source == null) {
288      return;
289    }
290    mapping.originalLine = originalPos.line;
291    mapping.originalColumn = originalPos.column;
292    newMappingList.push(mapping);
293  });
294  const updatedGenerator: sourceMap.SourceMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(newConsumer);
295  updatedGenerator._file = originMap.file;
296  updatedGenerator._mappings._array = newMappingList;
297  return JSON.parse(updatedGenerator.toString());
298}
299
300export function hasArkDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration |
301  ts.StructDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration, decortorName: string): boolean {
302  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
303  if (decorators && decorators.length) {
304    for (let i = 0; i < decorators.length; i++) {
305      const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim();
306      return originalDecortor === decortorName;
307    }
308  }
309  return false;
310}
311
312export const utUtils = {
313  writeFileContent
314};