1/* 2 * Copyright (c) 2021 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 16const fs = require('fs'); 17const path = require('path'); 18import Compilation from 'webpack/lib/Compilation'; 19import JavascriptModulesPlugin from 'webpack/lib/javascript/JavascriptModulesPlugin'; 20import CachedSource from 'webpack-sources/lib/CachedSource'; 21import ConcatSource from 'webpack-sources/lib/ConcatSource'; 22 23import { 24 circularFile, 25 useOSFiles, 26 mkDir, 27 elements 28} from './util'; 29import cluster from 'cluster'; 30 31let mStats; 32let mErrorCount = 0; 33let mWarningCount = 0; 34let isShowError = true; 35let isShowWarning = true; 36let isShowNote = true; 37let warningCount = 0; 38let noteCount = 0; 39let errorCount = 0; 40 41let GLOBAL_COMMON_MODULE_CACHE; 42 43/** 44 * Webpack plugin that manages compilation results, module caching, and asset processing 45 * Handles multiple aspects of the build process including: 46 * - Module discovery and caching 47 * - Common and i18n resource management 48 * - Build result reporting 49 * - Global module caching 50 */ 51class ResultStates { 52 constructor(options) { 53 this.options = options; 54 GLOBAL_COMMON_MODULE_CACHE = ` 55 globalThis["__common_module_cache__${process.env.hashProjectPath}"] =` + 56 ` globalThis["__common_module_cache__${process.env.hashProjectPath}"] || {};`; 57 } 58 59 apply(compiler) { 60 const buildPath = this.options.build; 61 const commonPaths = new Set(); 62 const i18nPaths = new Set(); 63 const cachePath = path.resolve(process.env.cachePath, process.env.DEVICE_LEVEL === 'rich' ? 64 '.rich_cache' : '.lite_cache'); 65 const entryFile = path.join(cachePath, 'entry.json'); 66 const entryPaths = new Set(); 67 68 compiler.hooks.compilation.tap('toFindModule', (compilation) => { 69 compilation.hooks.buildModule.tap("findModule", (module) => { 70 if (module.resource && fs.existsSync(module.resource)) { 71 entryPaths.add(module.resource); 72 } 73 if (module.context.indexOf(process.env.projectPath) >= 0) { 74 return; 75 } 76 const modulePath = path.join(module.context); 77 const srcIndex = modulePath.lastIndexOf(path.join('src', 'main', 'js')); 78 if (srcIndex < 0) { 79 return; 80 } 81 const commonPath = path.resolve(modulePath.substring(0, srcIndex), 82 'src', 'main', 'js', 'common'); 83 if (fs.existsSync(commonPath)) { 84 commonPaths.add(commonPath); 85 } 86 const i18nPath = path.resolve(modulePath.substring(0, srcIndex), 87 'src', 'main', 'js', 'i18n'); 88 if (fs.existsSync(i18nPath)) { 89 i18nPaths.add(i18nPath); 90 } 91 }); 92 }); 93 94 compiler.hooks.afterCompile.tap('copyFindModule', () => { 95 for (let commonPath of commonPaths) { 96 circularFile(commonPath, path.resolve(buildPath, '../share/common')); 97 } 98 for (let i18nPath of i18nPaths) { 99 circularFile(i18nPath, path.resolve(buildPath, '../share/i18n')); 100 } 101 addCacheFiles(entryFile, cachePath, entryPaths); 102 }); 103 104 compiler.hooks.done.tap('Result States', (stats) => { 105 Object.keys(elements).forEach(key => { 106 delete elements[key]; 107 }) 108 if (process.env.isPreview && process.env.aceSoPath && 109 useOSFiles && useOSFiles.size > 0) { 110 writeUseOSFiles(); 111 } 112 mStats = stats; 113 warningCount = 0; 114 noteCount = 0; 115 errorCount = 0; 116 if (mStats.compilation.errors) { 117 mErrorCount = mStats.compilation.errors.length; 118 } 119 if (mStats.compilation.warnings) { 120 mWarningCount = mStats.compilation.warnings.length; 121 } 122 if (process.env.error === 'false') { 123 isShowError = false; 124 } 125 if (process.env.warning === 'false') { 126 isShowWarning = false; 127 } 128 if (process.env.note === 'false') { 129 isShowNote = false; 130 } 131 printResult(buildPath); 132 }); 133 134 compiler.hooks.compilation.tap('CommonAsset', compilation => { 135 compilation.hooks.processAssets.tap( 136 { 137 name: 'GLOBAL_COMMON_MODULE_CACHE', 138 stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, 139 }, 140 (assets) => { 141 if (assets['commons.js']) { 142 assets['commons.js'] = new CachedSource( 143 new ConcatSource(assets['commons.js'], GLOBAL_COMMON_MODULE_CACHE)); 144 } else if (assets['vendors.js']) { 145 assets['vendors.js'] = new CachedSource( 146 new ConcatSource(assets['vendors.js'], GLOBAL_COMMON_MODULE_CACHE)); 147 } 148 } 149 ); 150 }); 151 152 compiler.hooks.compilation.tap('Require', compilation => { 153 JavascriptModulesPlugin.getCompilationHooks(compilation).renderRequire.tap('renderRequire', 154 (source) => { 155 return process.env.DEVICE_LEVEL === 'rich' ? `var commonCachedModule =` + 156 ` globalThis["__common_module_cache__${process.env.hashProjectPath}"] ? ` + 157 `globalThis["__common_module_cache__${process.env.hashProjectPath}"]` + 158 `[moduleId]: null;\n` + 159 `if (commonCachedModule) { return commonCachedModule.exports; }\n` + 160 source.replace('// Execute the module function', 161 `function isCommonModue(moduleId) { 162 if (globalThis["webpackChunk${process.env.hashProjectPath}"]) { 163 const length = globalThis["webpackChunk${process.env.hashProjectPath}"].length; 164 switch (length) { 165 case 1: 166 return globalThis["webpackChunk${process.env.hashProjectPath}"][0][1][moduleId]; 167 case 2: 168 return globalThis["webpackChunk${process.env.hashProjectPath}"][0][1][moduleId] || 169 globalThis["webpackChunk${process.env.hashProjectPath}"][1][1][moduleId]; 170 } 171 } 172 return undefined; 173 }\n` + 174 `if (globalThis["__common_module_cache__${process.env.hashProjectPath}"]` + 175 ` && String(moduleId).indexOf("?name=") < 0 && isCommonModue(moduleId)) {\n` + 176 ` globalThis["__common_module_cache__${process.env.hashProjectPath}"]` + 177 `[moduleId] = module;\n}`) : source; 178 }); 179 }); 180 } 181} 182 183/** 184 * Manages cache files for build entries by reading existing cache and updating with new entries 185 * 186 * @param {string} entryFile - Path to the cache file storing entry paths 187 * @param {string} cachePath - Directory path where cache files are stored 188 * @param {Set} entryPaths - Set containing new entry paths to cache 189 */ 190function addCacheFiles(entryFile, cachePath, entryPaths) { 191 const entryArray = []; 192 if (fs.existsSync(entryFile)) { 193 const oldArray = JSON.parse(fs.readFileSync(entryFile)); 194 oldArray.forEach(element => { 195 entryPaths.add(element); 196 }) 197 } else if (!(process.env.tddMode === 'true') && !(fs.existsSync(cachePath) && fs.statSync(cachePath).isDirectory())) { 198 mkDir(cachePath); 199 } 200 entryArray.push(...entryPaths); 201 if (fs.existsSync(cachePath) && fs.statSync(cachePath).isDirectory()) { 202 fs.writeFileSync(entryFile, JSON.stringify(entryArray)); 203 } 204} 205 206const red = '\u001b[31m'; 207const yellow = '\u001b[33m'; 208const blue = '\u001b[34m'; 209const reset = '\u001b[39m'; 210 211/** 212 * Writes compilation errors to a log file in the build directory 213 * 214 * @param {string} buildPath - The directory path where the log file should be created 215 * @param {string} content - The error content to write to the log file 216 */ 217const writeError = (buildPath, content) => { 218 fs.writeFile(path.resolve(buildPath, 'compile_error.log'), content, (err) => { 219 if (err) { 220 return console.error(err); 221 } 222 }); 223}; 224 225/** 226 * Prints the final compilation result with detailed error/warning statistics 227 * Handles both regular builds and preview modes with appropriate output formatting 228 * 229 * @param {string} buildPath - The build directory path for error logging 230 */ 231function printResult(buildPath) { 232 printWarning(); 233 printError(buildPath); 234 if (errorCount + warningCount + noteCount > 0 || process.env.abcCompileSuccess === 'false') { 235 let result; 236 const resultInfo = {}; 237 if (errorCount > 0) { 238 resultInfo.ERROR = errorCount; 239 result = 'FAIL '; 240 } else { 241 result = 'SUCCESS '; 242 } 243 244 if (process.env.abcCompileSuccess === 'false') { 245 result = 'FAIL '; 246 } 247 248 if (warningCount > 0) { 249 resultInfo.WARN = warningCount; 250 } 251 252 if (noteCount > 0) { 253 resultInfo.NOTE = noteCount; 254 } 255 if (result === 'SUCCESS ' && process.env.isPreview === 'true') { 256 printPreviewResult(resultInfo); 257 } else { 258 console.log(blue, 'COMPILE RESULT:' + result + JSON.stringify(resultInfo), reset); 259 } 260 } else { 261 if (process.env.isPreview === 'true') { 262 printPreviewResult(); 263 } else { 264 console.log(blue, 'COMPILE RESULT:SUCCESS ', reset); 265 } 266 } 267 clearArkCompileStatus(); 268} 269 270/** 271 * Resets the Ark compilation status flag to indicate successful compilation 272 * Sets the environment variable 'abcCompileSuccess' to 'true' 273 */ 274function clearArkCompileStatus() { 275 process.env.abcCompileSuccess = 'true'; 276} 277 278/** 279 * Prints the preview build result after checking worker processes 280 * Only displays success message when all worker processes have completed 281 * 282 * @param {string} resultInfo - Additional result information to display (defaults to empty string) 283 */ 284function printPreviewResult(resultInfo = "") { 285 let workerNum = Object.keys(cluster.workers).length; 286 if (workerNum === 0) { 287 printSuccessInfo(resultInfo); 288 } 289} 290 291/** 292 * Prints compilation success information to the console with formatted output 293 * Displays either a simple success message or detailed result information 294 * 295 * @param {Array|Object} resultInfo - Additional success information to display 296 */ 297function printSuccessInfo(resultInfo) { 298 if (resultInfo.length === 0) { 299 console.log(blue, 'COMPILE RESULT:SUCCESS ', reset); 300 } else { 301 console.log(blue, 'COMPILE RESULT:SUCCESS ' + JSON.stringify(resultInfo), reset); 302 } 303} 304 305/** 306 * Processes and prints compilation warnings and notes with formatted output 307 * Handles different types of warning messages with color coding and filtering 308 */ 309function printWarning() { 310 if (mWarningCount > 0) { 311 const warnings = mStats.compilation.warnings; 312 const length = warnings.length; 313 for (let index = 0; index < length; index++) { 314 let message = warnings[index].message 315 if (message.match(/noteStart(([\s\S])*)noteEnd/) !== null) { 316 noteCount++; 317 if (isShowNote) { 318 console.info(' ' + message.match(/noteStart(([\s\S])*)noteEnd/)[1].trim(), reset, '\n') 319 } 320 } else if (message.match(/warnStart(([\s\S])*)warnEnd/) !== null) { 321 warningCount++; 322 if (isShowWarning) { 323 console.warn(yellow, message.match(/warnStart(([\s\S])*)warnEnd/)[1].trim(), reset, '\n') 324 } 325 } 326 } 327 if (mWarningCount > length) { 328 warningCount = warningCount + mWarningCount - length; 329 } 330 } 331} 332 333/** 334 * Formats and prints compilation errors to console and writes to error log file 335 * Handles both custom-formatted errors and standard webpack errors 336 * 337 * @param {string} buildPath - The build directory path for error logging 338 */ 339function printError(buildPath) { 340 if (mErrorCount > 0) { 341 const errors = mStats.compilation.errors; 342 const length = errors.length; 343 if (isShowError) { 344 let errorContent = ''; 345 for (let index = 0; index < length; index++) { 346 if (errors[index]) { 347 let message = errors[index].message 348 if (message) { 349 if (message.match(/errorStart(([\s\S])*)errorEnd/) !== null) { 350 const errorMessage = message.match(/errorStart(([\s\S])*)errorEnd/)[1]; 351 console.error(red, errorMessage.trim(), reset, '\n'); 352 } else { 353 const messageArrary = message.split('\n') 354 let logContent = '' 355 messageArrary.forEach(element => { 356 if (!(/^at/.test(element.trim()))) { 357 logContent = logContent + element + '\n' 358 } 359 }); 360 console.error(red, logContent, reset, '\n'); 361 } 362 errorCount ++; 363 errorContent += message 364 } 365 } 366 } 367 writeError(buildPath, errorContent); 368 } 369 } 370} 371 372/** 373 * Writes a list of operating system-specific files to a tracking file 374 * Maintains a cumulative record of OS-specific files across builds 375 */ 376function writeUseOSFiles() { 377 let oldInfo = ''; 378 if (!fs.existsSync(process.env.aceSoPath)) { 379 const parent = path.join(process.env.aceSoPath, '..'); 380 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 381 mkDir(parent); 382 } 383 } else { 384 oldInfo = fs.readFileSync(process.env.aceSoPath, 'utf-8') + '\n'; 385 } 386 fs.writeFileSync(process.env.aceSoPath, oldInfo + Array.from(useOSFiles).join('\n')); 387} 388 389module.exports = ResultStates; 390