• 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 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