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