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