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 rollupObject 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 * as ts from 'typescript'; 17import MagicString from 'magic-string'; 18import { 19 GEN_ABC_PLUGIN_NAME, 20 PACKAGES 21} from '../common/ark_define'; 22import { 23 getOhmUrlByFilepath, 24 getOhmUrlByHarName, 25 getOhmUrlBySystemApiOrLibRequest 26} from '../../../ark_utils'; 27import { writeFileSyncByNode } from '../../../process_module_files'; 28import { 29 isJsonSourceFile, 30 isJsSourceFile, 31 writeFileContentToTempDir 32} from '../utils'; 33 34const ROLLUP_IMPORT_NODE: string = 'ImportDeclaration'; 35const ROLLUP_EXPORTNAME_NODE: string = 'ExportNamedDeclaration'; 36const ROLLUP_EXPORTALL_NODE: string = 'ExportAllDeclaration'; 37 38export class ModuleSourceFile { 39 private static sourceFiles: ModuleSourceFile[] = []; 40 private moduleId: string; 41 private source: string | ts.SourceFile; 42 private isSourceNode: boolean = false; 43 private static projectConfig: any; 44 private static logger: any; 45 46 constructor(moduleId: string, source: string | ts.SourceFile) { 47 this.moduleId = moduleId; 48 this.source = source; 49 if (typeof this.source !== 'string') { 50 this.isSourceNode = true; 51 } 52 } 53 54 static newSourceFile(moduleId: string, source: string | ts.SourceFile) { 55 ModuleSourceFile.sourceFiles.push(new ModuleSourceFile(moduleId, source)); 56 } 57 58 static async processModuleSourceFiles(rollupObject: any) { 59 this.initPluginEnv(rollupObject); 60 for (const source of ModuleSourceFile.sourceFiles) { 61 if (!rollupObject.share.projectConfig.compileHar) { 62 // compileHar: compile closed source har of project, which convert .ets to .d.ts and js, doesn't transform module request. 63 source.processModuleRequest(rollupObject); 64 } 65 await source.writeSourceFile(); 66 } 67 ModuleSourceFile.sourceFiles = []; 68 } 69 70 private async writeSourceFile() { 71 if (this.isSourceNode && !isJsSourceFile(this.moduleId)) { 72 writeFileSyncByNode(<ts.SourceFile>this.source, true, ModuleSourceFile.projectConfig); 73 } else { 74 await writeFileContentToTempDir(this.moduleId, <string>this.source, ModuleSourceFile.projectConfig, 75 ModuleSourceFile.logger); 76 } 77 } 78 79 private getOhmUrl(rollupObject: any, moduleRequest: string, filePath: string | undefined): string | undefined { 80 let systemOrLibOhmUrl: string | undefined = getOhmUrlBySystemApiOrLibRequest(moduleRequest); 81 if (systemOrLibOhmUrl != undefined) { 82 return systemOrLibOhmUrl; 83 } 84 const harOhmUrl: string | undefined = getOhmUrlByHarName(moduleRequest, ModuleSourceFile.projectConfig); 85 if (harOhmUrl !== undefined) { 86 return harOhmUrl; 87 } 88 if (filePath) { 89 const targetModuleInfo: any = rollupObject.getModuleInfo(filePath); 90 const namespace: string = targetModuleInfo['meta']['moduleName']; 91 const ohmUrl: string = 92 getOhmUrlByFilepath(filePath, ModuleSourceFile.projectConfig, ModuleSourceFile.logger, namespace); 93 return ohmUrl.startsWith(PACKAGES) ? `@package:${ohmUrl}` : `@bundle:${ohmUrl}`; 94 } 95 return undefined; 96 } 97 98 private processJsModuleRequest(rollupObject: any) { 99 const moduleInfo: any = rollupObject.getModuleInfo(this.moduleId); 100 const importMap: any = moduleInfo.importedIdMaps; 101 const REG_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^'"]+)['"]/g; 102 this.source = (<string>this.source).replace(REG_DEPENDENCY, (item, moduleRequest) => { 103 const ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest]); 104 if (ohmUrl !== undefined) { 105 item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 106 return quotation + ohmUrl + quotation; 107 }); 108 } 109 return item; 110 }); 111 } 112 113 private processTransformedJsModuleRequest(rollupObject: any) { 114 const moduleInfo: any = rollupObject.getModuleInfo(this.moduleId); 115 const importMap: any = moduleInfo.importedIdMaps; 116 const code: MagicString = new MagicString(<string>this.source); 117 const ast = moduleInfo.ast; 118 ast.body.forEach(node => { 119 if (node.type === ROLLUP_IMPORT_NODE || (node.type === ROLLUP_EXPORTNAME_NODE && node.source) || 120 node.type === ROLLUP_EXPORTALL_NODE) { 121 const ohmUrl: string | undefined = 122 this.getOhmUrl(rollupObject, node.source.value, importMap[node.source.value]); 123 if (ohmUrl !== undefined) { 124 code.update(node.source.start, node.source.end, `'${ohmUrl}'`); 125 } 126 } 127 }); 128 this.source = code.toString(); 129 } 130 131 private processTransformedTsModuleRequest(rollupObject: any) { 132 const moduleInfo: any = rollupObject.getModuleInfo(this.moduleId); 133 const importMap: any = moduleInfo.importedIdMaps; 134 const statements: ts.Statement[] = []; 135 (<ts.SourceFile>this.source)!.forEachChild((childNode: ts.Statement) => { 136 if (ts.isImportDeclaration(childNode) || (ts.isExportDeclaration(childNode) && childNode.moduleSpecifier)) { 137 // moduleSpecifier.getText() returns string carrying on quotation marks which the importMap's key does not, 138 // so we need to remove the quotation marks from moduleRequest. 139 const moduleRequest: string = childNode.moduleSpecifier.getText().replace(/'|"/g, ''); 140 const ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest]); 141 if (ohmUrl !== undefined) { 142 if (ts.isImportDeclaration(childNode)) { 143 childNode = ts.factory.updateImportDeclaration(childNode, childNode.decorators, childNode.modifiers, 144 childNode.importClause, ts.factory.createStringLiteral(ohmUrl)); 145 } else { 146 childNode = ts.factory.updateExportDeclaration(childNode, childNode.decorators, childNode.modifiers, 147 childNode.isTypeOnly, childNode.exportClause, ts.factory.createStringLiteral(ohmUrl)); 148 } 149 } 150 } 151 statements.push(childNode); 152 }); 153 this.source = ts.factory.updateSourceFile(<ts.SourceFile>this.source, statements); 154 } 155 156 // Replace each module request in source file to a unique representation which is called 'ohmUrl'. 157 // This 'ohmUrl' will be the same as the record name for each file, to make sure runtime can find the corresponding 158 // record based on each module request. 159 processModuleRequest(rollupObject: any) { 160 if (isJsonSourceFile(this.moduleId)) { 161 return; 162 } 163 if (isJsSourceFile(this.moduleId)) { 164 this.processJsModuleRequest(rollupObject); 165 return; 166 } 167 168 // Only when files were transformed to ts, the corresponding ModuleSourceFile were initialized with sourceFile node, 169 // if files were transformed to js, ModuleSourceFile were initialized with srouce string. 170 this.isSourceNode ? this.processTransformedTsModuleRequest(rollupObject) : 171 this.processTransformedJsModuleRequest(rollupObject); 172 } 173 174 private static initPluginEnv(rollupObject: any) { 175 this.projectConfig = Object.assign(rollupObject.share.arkProjectConfig, rollupObject.share.projectConfig); 176 this.logger = rollupObject.share.getLogger(GEN_ABC_PLUGIN_NAME); 177 } 178} 179