• 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 fs from 'fs';
17import path from 'path';
18
19import { ModuleMode } from './module_mode';
20import {
21  blue,
22  reset,
23  MODULES_ABC,
24  SOURCEMAPS,
25  SYMBOLMAP
26} from '../common/ark_define';
27import { isJsonSourceFile } from '../utils';
28import {
29  mkdirsSync,
30  toUnixPath,
31  validateFilePathLength
32} from '../../../utils';
33import {
34  createAndStartEvent,
35  stopEvent
36} from '../../../ark_utils';
37import { SourceMapGenerator } from '../generate_sourcemap';
38import {
39  ArkTSInternalErrorDescription,
40  ErrorCode
41} from '../error_code';
42import {
43  LogData,
44  LogDataFactory
45} from '../logger';
46
47let isFirstBuild: boolean = true;
48
49export class ModuleHotreloadMode extends ModuleMode {
50  symbolMapFilePath: string;
51  constructor(rollupObject: Object) {
52    super(rollupObject);
53    if (this.projectConfig.oldMapFilePath) {
54      this.symbolMapFilePath = path.join(this.projectConfig.oldMapFilePath, SYMBOLMAP);
55    } else {
56      const errInfo: LogData = LogDataFactory.newInstance(
57        ErrorCode.ETS2BUNDLE_INTERNAL_HOT_RELOAD_FAILED_INCORRECT_SYMBOL_MAP_CONFIG,
58        ArkTSInternalErrorDescription,
59        'Hot Reload failed, symbolMap file is not correctly configured.'
60      );
61      this.logger.printErrorAndExit(errInfo);
62    }
63  }
64
65  generateAbc(rollupObject: Object, parentEvent: Object): void {
66    // To support hotreload of multiple HSP modules, rollup no longer runs in watch mode.
67    // In this case isFirstBuild needs to be passed in by hvigor, because if multiple HSP
68    // module build tasks are running in the same worker in the IDE, the isFirstBuild of
69    // the later build task will be overwritten by the first build task to false.
70    if (rollupObject.share.projectConfig.watchMode !== 'true') {
71      isFirstBuild = this.projectConfig.isFirstBuild;
72    }
73    if (isFirstBuild) {
74      this.compileAllFiles(rollupObject, parentEvent);
75      if (rollupObject.share.projectConfig.watchMode === 'true') {
76        isFirstBuild = false;
77      }
78    } else {
79      this.compileChangeListFiles(rollupObject, parentEvent);
80    }
81  }
82
83  addHotReloadArgs(): void {
84    if (isFirstBuild) {
85      this.cmdArgs.push('--dump-symbol-table');
86      this.cmdArgs.push(`"${this.symbolMapFilePath}"`);
87      return;
88    }
89    this.addCacheFileArgs();
90    this.cmdArgs.push('--input-symbol-table');
91    this.cmdArgs.push(`"${this.symbolMapFilePath}"`);
92    this.cmdArgs.push('--hot-reload');
93  }
94
95  private compileAllFiles(rollupObject: Object, parentEvent: Object): void {
96    this.prepareForCompilation(rollupObject, parentEvent);
97    SourceMapGenerator.getInstance().buildModuleSourceMapInfo(parentEvent);
98    this.generateAbcByEs2abc(parentEvent, !!this.projectConfig.byteCodeHarInfo);
99  }
100
101  private compileChangeListFiles(rollupObject: Object, parentEvent: Object): void {
102    if (!fs.existsSync(this.projectConfig.changedFileList)) {
103      this.logger.debug(blue, `ArkTS: Cannot find file: ${
104        this.projectConfig.changedFileList}, skip hot reload build`, reset);
105      return;
106    }
107
108    const changedFileListJson: string = fs.readFileSync(this.projectConfig.changedFileList).toString();
109    const {
110      changedFileListVersion,
111      changedFileList
112    } = this.parseChangedFileListJson(changedFileListJson);
113    if (typeof changedFileList === 'undefined' || changedFileList.length === 0) {
114      this.logger.debug(blue, `ArkTS: No changed files found, skip hot reload build`, reset);
115      return;
116    }
117
118    let needHotreloadBuild: boolean = true;
119    let changedFileListInAbsolutePath: Array<string> = changedFileList.map((file) => {
120      if (isJsonSourceFile(file)) {
121        this.logger.debug(blue, `ARKTS: json source file: ${file} changed, skip hot reload build!`, reset);
122        needHotreloadBuild = false;
123      }
124      return changedFileListVersion === 'v1' ? path.join(this.projectConfig.projectPath, file) : file;
125    });
126
127    if (!needHotreloadBuild) {
128      return;
129    }
130
131    const eventCollectModuleFileList = createAndStartEvent(parentEvent, 'collect module file list');
132    this.collectModuleFileList(rollupObject, changedFileListInAbsolutePath[Symbol.iterator]());
133    stopEvent(eventCollectModuleFileList);
134
135    if (!fs.existsSync(this.projectConfig.patchAbcPath)) {
136      mkdirsSync(this.projectConfig.patchAbcPath);
137    }
138
139    this.updateSourceMapFromFileList(
140      SourceMapGenerator.getInstance().isNewSourceMaps() ? changedFileListInAbsolutePath : changedFileList,
141      parentEvent);
142    const outputABCPath: string = path.join(this.projectConfig.patchAbcPath, MODULES_ABC);
143    validateFilePathLength(outputABCPath, this.logger);
144    this.moduleAbcPath = outputABCPath;
145    // During incremental compilation, the bytecode har path must be blocked from being written to filesInfo.
146    this.generateAbcByEs2abc(parentEvent, false);
147  }
148
149  private parseChangedFileListJson(changedFileListJson: string): object {
150    const changedFileList = JSON.parse(changedFileListJson);
151    if (Object.prototype.hasOwnProperty.call(changedFileList, 'modifiedFilesV2')) {
152      return {
153        'changedFileListVersion': 'v2',
154        'changedFileList': changedFileList.modifiedFilesV2
155          .filter(file => Object.prototype.hasOwnProperty.call(file, 'filePath'))
156          .map(file => file.filePath)
157      };
158    } else if (Object.prototype.hasOwnProperty.call(changedFileList, 'modifiedFiles')) {
159      return {
160        'changedFileListVersion': 'v1',
161        'changedFileList': changedFileList.modifiedFiles
162      };
163    } else {
164      return {
165        'changedFileListVersion': '',
166        'changedFileList': []
167      };
168    }
169  }
170
171  private updateSourceMapFromFileList(fileList: Array<string>, parentEvent: Object): void {
172    const eventUpdateSourceMapFromFileList = createAndStartEvent(parentEvent, 'update source map from file list');
173    const sourceMapGenerator = SourceMapGenerator.getInstance();
174    const relativeProjectPath: string = this.projectConfig.projectPath.slice(
175      this.projectConfig.projectRootPath.length + path.sep.length);
176    let hotReloadSourceMap: Object = {};
177    for (const file of fileList) {
178      if (sourceMapGenerator.isNewSourceMaps()) {
179        validateFilePathLength(file, this.logger);
180        hotReloadSourceMap[sourceMapGenerator.genKey(file)] = sourceMapGenerator.getSourceMap(file);
181      } else {
182        const sourceMapPath: string = toUnixPath(path.join(relativeProjectPath, file));
183        validateFilePathLength(sourceMapPath, this.logger);
184        hotReloadSourceMap[sourceMapPath] = sourceMapGenerator.getSourceMap(sourceMapPath);
185      }
186    }
187    if (!sourceMapGenerator.isNewSourceMaps()) {
188      sourceMapGenerator.modifySourceMapKeyToCachePath(hotReloadSourceMap);
189    }
190    const sourceMapFilePath: string = path.join(this.projectConfig.patchAbcPath, SOURCEMAPS);
191    validateFilePathLength(sourceMapFilePath, this.logger);
192    fs.writeFileSync(sourceMapFilePath,
193      JSON.stringify(hotReloadSourceMap, null, 2), 'utf-8');
194    stopEvent(eventUpdateSourceMapFromFileList);
195  }
196
197  private generateAbcByEs2abc(parentEvent: Object, includeByteCodeHarInfo: boolean): void {
198    this.generateEs2AbcCmd();
199    this.addHotReloadArgs();
200    this.genDescriptionsForMergedEs2abc(includeByteCodeHarInfo);
201    this.generateMergedAbcOfEs2Abc(parentEvent);
202  }
203}
204