• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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