• 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 MagicString, { SourceMap } from 'magic-string';
17import { createFilter } from '@rollup/pluginutils';
18import path from 'path';
19import {
20  NATIVE_MODULE,
21  ARKUI_X_PLUGIN,
22  GLOBAL_THIS_REQUIRE_NATIVE_MODULE,
23  GLOBAL_THIS_REQUIRE_NAPI
24} from '../../pre_define';
25import {
26  systemModules,
27  projectConfig,
28  sdkConfigs,
29  sdkConfigPrefix,
30  extendSdkConfigs
31} from '../../../main';
32
33import {
34  writeUseOSFiles,
35  writeCollectionFile,
36  getAllComponentsOrModules
37} from '../../utils';
38import { appComponentCollection } from '../../ets_checker';
39import { hasTsNoCheckOrTsIgnoreFiles } from '../ark_compiler/utils';
40import { shouldEmitJsFlagById } from '../ets_ui/rollup-plugin-ets-typescript';
41
42const filterCrossplatform: (id: string) => boolean = createFilter(/(?<!\.d)\.(ets|ts|js)$/);
43const filter: (id: string) => boolean = createFilter(/(?<!\.d)\.(ets|ts)$/);
44const allFiles: Set<string> = new Set();
45
46export const appImportModuleCollection: Map<string, Set<string>> = new Map();
47export const kitModules: Map<string, Map<string, Set<string>>> = new Map();
48
49export function apiTransform() {
50  const useOSFiles: Set<string> = new Set();
51  let needModuleCollection: boolean = false;
52  let needComponentCollection: boolean = false;
53  return {
54    name: 'apiTransform',
55    load(id: string): void {
56      allFiles.add(path.join(id));
57    },
58    buildStart(): void {
59      if (this.share.projectConfig.isCrossplatform) {
60        needModuleCollection = true;
61        needComponentCollection = true;
62      } else if (this.share.projectConfig.widgetCompile) {
63        needModuleCollection = false;
64        needComponentCollection = true;
65      }
66    },
67    transform(code: string, id: string): {
68      code: string;
69      map: SourceMap;
70    } {
71      let hiresStatus: boolean = this.share.projectConfig.needCompleteSourcesMap;
72      const shouldEmitJsFlag: boolean = id.endsWith('.js') ||
73        shouldEmitJsFlagById(id) || projectConfig.compileMode !== 'esmodule';
74      if (!shouldEmitJsFlag &&
75        !this.share.projectConfig.isCrossplatform &&
76        !this.share.projectConfig.widgetCompile) {
77        return null;
78      }
79
80      if (projectConfig.isCrossplatform ? filterCrossplatform(id) : filter(id)) {
81        if (projectConfig.compileMode === 'esmodule') {
82          code = processSystemApiAndLibso(code, id, useOSFiles);
83          hiresStatus = hiresStatus || hasTsNoCheckOrTsIgnoreFiles.includes(id) ?
84            true :
85            false;
86        } else {
87          code = processSystemApi(code, id);
88          code = processLibso(code, id, useOSFiles);
89          hiresStatus = true;
90        }
91      }
92      if (!shouldEmitJsFlag) {
93        return null;
94      }
95      const magicString: MagicString = new MagicString(code);
96      return {
97        code: code,
98        map: magicString.generateMap({ hires: hiresStatus })
99      };
100    },
101    beforeBuildEnd(): void {
102      this.share.allFiles = allFiles;
103      if (process.env.watchMode !== 'true' && !projectConfig.xtsMode && needComponentCollection) {
104        let widgetPath: string;
105        if (projectConfig.widgetCompile) {
106          widgetPath = path.resolve(projectConfig.aceModuleBuild, 'widget');
107        }
108        this.share.allComponents = getAllComponentsOrModules(allFiles, 'component_collection.json');
109        writeCollectionFile(projectConfig.cachePath, appComponentCollection,
110          this.share.allComponents, 'component_collection.json', this.share.allFiles, widgetPath);
111      }
112    },
113    buildEnd(): void {
114      if (projectConfig.isPreview && projectConfig.aceSoPath &&
115        useOSFiles && useOSFiles.size > 0) {
116        writeUseOSFiles(useOSFiles);
117      }
118      if (process.env.watchMode !== 'true' && !projectConfig.xtsMode && needModuleCollection) {
119        replaceKitModules();
120        const allModules: Map<string, Array<string>> = getAllComponentsOrModules(allFiles, 'module_collection.json');
121        writeCollectionFile(projectConfig.cachePath, appImportModuleCollection, allModules, 'module_collection.json');
122      }
123    },
124    cleanUp(): void {
125      allFiles.clear();
126      appImportModuleCollection.clear();
127      useOSFiles.clear();
128      kitModules.clear();
129    }
130  };
131}
132
133
134function processSystemApi(content: string, sourcePath: string): string {
135  // 'arkui-x' represents cross platform related APIs, processed as 'ohos'
136  const REG_SYSTEM: RegExp =
137    new RegExp(`import\\s+(.+)\\s+from\\s+['"]@(${sdkConfigPrefix})\\.(\\S+)['"]|import\\s+(.+)\\s*=\\s*require\\(\\s*['"]@(${sdkConfigPrefix})\\.(\\S+)['"]\\s*\\)`, 'g');
138  appImportModuleCollection.set(path.join(sourcePath), new Set());
139  return content.replace(REG_SYSTEM, (item, item1, item2, item3, item4, item5, item6) => {
140    const moduleType: string = item2 || item5;
141    const systemKey: string = item3 || item6;
142    const systemValue: string = item1 || item4;
143    const systemModule: string = `${moduleType}.${systemKey}`;
144    appImportModuleCollection.get(path.join(sourcePath)).add(systemModule);
145    checkModuleExist(systemModule, sourcePath);
146    const externalModuleParam: string = isExtendModuleType(moduleType) &&
147      moduleType !== ARKUI_X_PLUGIN ? `, false, '', '${moduleType}'` : ``;
148    if (NATIVE_MODULE.has(systemModule)) {
149      item = `var ${systemValue} = ${GLOBAL_THIS_REQUIRE_NATIVE_MODULE}('${moduleType}.${systemKey}')`;
150    } else if (checkModuleType(moduleType)) {
151      item = `var ${systemValue} = ${GLOBAL_THIS_REQUIRE_NAPI}('${systemKey}'${externalModuleParam})`;
152    }
153    return item;
154  });
155}
156
157interface SdkConfig {
158  apiPath: string;
159  prefix: string;
160}
161
162function isExtendModuleType(moduleType: string): boolean {
163  for (let i = 0; i < extendSdkConfigs.length; i++) {
164    const config: SdkConfig = extendSdkConfigs[i];
165    if (config.prefix === `@${moduleType}`) {
166      return true;
167    }
168  }
169  return false;
170}
171
172function checkModuleType(moduleType: string): boolean {
173  for (let i = 0; i < sdkConfigs.length; i++) {
174    const config = sdkConfigs[i];
175    if (config.prefix === `@${moduleType}`) {
176      return true;
177    }
178  }
179  return false;
180}
181
182function checkModuleExist(systemModule: string, sourcePath: string): void {
183  const module: string = `@${systemModule.trim()}.d.ts`;
184  if (/\.js$/.test(sourcePath) && !systemModules.includes(module)) {
185    const message: string =
186      `Cannot find module '${module}' or its corresponding type declarations.`;
187    console.error(`BUILDERROR File: ${sourcePath}\n ${message}`);
188  }
189}
190
191function processLibso(content: string, sourcePath: string, useOSFiles: Set<string>): string {
192  const REG_LIB_SO: RegExp =
193    /import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g;
194  return content.replace(REG_LIB_SO, (_, item1, item2, item3, item4) => {
195    useOSFiles.add(sourcePath);
196    const libSoValue: string = item1 || item3;
197    const libSoKey: string = item2 || item4;
198    return projectConfig.bundleName && projectConfig.moduleName
199      ? `var ${libSoValue} = globalThis.requireNapi("${libSoKey}", true, "${projectConfig.bundleName}/${projectConfig.moduleName}");`
200      : `var ${libSoValue} = globalThis.requireNapi("${libSoKey}", true);`;
201  });
202}
203
204// It is rare to use `import xxx = require('module')` for system module and user native library,
205// Here keep tackling with this for compatibility concern.
206function processSystemApiAndLibso(content: string, sourcePath: string, useOSFiles: Set<string>): string {
207  // 'arkui-x' represents cross platform related APIs, processed as 'ohos'
208  const REG_REQUIRE_SYSTEM: RegExp = new RegExp(`import\\s+(.+)\\s*=\\s*require\\(\\s*['"]@(${sdkConfigPrefix})\\.(\\S+)['"]\\s*\\)`, 'g');
209  // Import libso should be recored in useOSFiles.
210  const REG_LIB_SO: RegExp =
211    /import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g;
212  // 'arkui-x' represents cross platform related APIs, processed as 'ohos'
213  const REG_IMPORT_SYSTEM = new RegExp(`import\\s+(.+)\\s+from\\s+['"]@(${sdkConfigPrefix})\\.(\\S+)['"]`, 'g');
214  appImportModuleCollection.set(path.join(sourcePath), new Set());
215  content.replace(REG_IMPORT_SYSTEM, (_, item1, item2, item3, item4, item5, item6) => {
216    const moduleType: string = item2 || item5;
217    const systemKey: string = item3 || item6;
218    const systemValue: string = item1 || item4;
219    const systemModule: string = `${moduleType}.${systemKey}`;
220    appImportModuleCollection.get(path.join(sourcePath)).add(systemModule);
221    return _;
222  });
223  return content.replace(REG_REQUIRE_SYSTEM, (_, item1, item2, item3, item4, item5, item6) => {
224    const moduleType: string = item2 || item5;
225    const systemKey: string = item3 || item6;
226    const systemValue: string = item1 || item4;
227    const systemModule: string = `${moduleType}.${systemKey}`;
228    appImportModuleCollection.get(path.join(sourcePath)).add(systemModule);
229    checkModuleExist(systemModule, sourcePath);
230    return `import ${systemValue} from '@${moduleType}.${systemKey}'`;
231  }).replace(REG_LIB_SO, (_, item1, item2, item3, item4) => {
232    useOSFiles.add(sourcePath);
233    const libSoValue: string = item1 || item3;
234    const libSoKey: string = item2 || item4;
235    return `import ${libSoValue} from 'lib${libSoKey}.so'`;
236  });
237}
238
239export function collectKitModules(fileName: string, key: string, value: string): void {
240  key = key.replace('@', '');
241  value = value.replace('@', '');
242  const currentValue: Map<string, Set<string>> = kitModules.get(fileName);
243  if (currentValue) {
244    const currentModuleName: Set<string> = currentValue.get(key);
245    if (currentModuleName && !currentModuleName.has(value)) {
246      currentModuleName.add(value);
247      currentValue.set(key, currentModuleName);
248    } else if (!currentModuleName) {
249      const moduleSet: Set<string> = new Set();
250      currentValue.set(key, moduleSet.add(value));
251    }
252  } else {
253    const moduleMap: Map<string, Set<string>> = new Map();
254    const moduleSet: Set<string> = new Set();
255    moduleMap.set(key, moduleSet.add(value));
256    kitModules.set(fileName, moduleMap);
257  }
258}
259
260function compareKitModules(value: Set<string>, kitKey: string, kitValue: Map<string, Set<string>>): void {
261  const realModules: Set<string> = new Set();
262  value.forEach((element: string) => {
263    if (kitValue.get(element)) {
264      kitValue.get(element).forEach((kitModuleRequest: string) => {
265        realModules.add(kitModuleRequest.replace(/\.d\.e?ts$/, ''));
266      });
267    } else {
268      realModules.add(element);
269    }
270  });
271  appImportModuleCollection.set(kitKey, realModules);
272}
273
274function replaceKitModules(): void {
275  appImportModuleCollection.forEach((value: Set<string>, key: string) => {
276    kitModules.forEach((kitValue: Map<string, Set<string>>, kitKey: string) => {
277      if (key === kitKey && value && value.size > 0) {
278        compareKitModules(value, kitKey, kitValue);
279      }
280    });
281  });
282}
283