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