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
43class ResultStates {
44  constructor(options) {
45    this.options = options;
46    GLOBAL_COMMON_MODULE_CACHE = `
47      globalThis["__common_module_cache__${process.env.hashProjectPath}"] =` +
48      ` globalThis["__common_module_cache__${process.env.hashProjectPath}"] || {};`;
49  }
50
51  apply(compiler) {
52    const buildPath = this.options.build;
53    const commonPaths = new Set();
54    const i18nPaths = new Set();
55    const cachePath = path.resolve(process.env.cachePath, process.env.DEVICE_LEVEL === 'rich' ?
56                                  '.rich_cache' : '.lite_cache');
57    const entryFile = path.join(cachePath, 'entry.json');
58    const entryPaths = new Set();
59
60    compiler.hooks.compilation.tap('toFindModule', (compilation) => {
61      compilation.hooks.buildModule.tap("findModule", (module) => {
62        if (module.resource && fs.existsSync(module.resource)) {
63          entryPaths.add(module.resource);
64        }
65        if (module.context.indexOf(process.env.projectPath) >= 0) {
66          return;
67        }
68        const modulePath = path.join(module.context);
69        const srcIndex = modulePath.lastIndexOf(path.join('src', 'main', 'js'));
70        if (srcIndex < 0) {
71          return;
72        }
73        const commonPath = path.resolve(modulePath.substring(0, srcIndex),
74          'src', 'main', 'js', 'common');
75        if (fs.existsSync(commonPath)) {
76          commonPaths.add(commonPath);
77        }
78        const i18nPath = path.resolve(modulePath.substring(0, srcIndex),
79          'src', 'main', 'js', 'i18n');
80        if (fs.existsSync(i18nPath)) {
81          i18nPaths.add(i18nPath);
82        }
83      });
84    });
85
86    compiler.hooks.afterCompile.tap('copyFindModule', () => {
87      for (let commonPath of commonPaths) {
88        circularFile(commonPath, path.resolve(buildPath, '../share/common'));
89      }
90      for (let i18nPath of i18nPaths) {
91        circularFile(i18nPath, path.resolve(buildPath, '../share/i18n'));
92      }
93      addCacheFiles(entryFile, cachePath, entryPaths);
94    });
95
96    compiler.hooks.done.tap('Result States', (stats) => {
97      Object.keys(elements).forEach(key => {
98        delete elements[key];
99      })
100      if (process.env.isPreview && process.env.aceSoPath &&
101        useOSFiles && useOSFiles.size > 0) {
102          writeUseOSFiles();
103      }
104      mStats = stats;
105      warningCount = 0;
106      noteCount = 0;
107      errorCount = 0;
108      if (mStats.compilation.errors) {
109        mErrorCount = mStats.compilation.errors.length;
110      }
111      if (mStats.compilation.warnings) {
112        mWarningCount = mStats.compilation.warnings.length;
113      }
114      if (process.env.error === 'false') {
115        isShowError = false;
116      }
117      if (process.env.warning === 'false') {
118        isShowWarning = false;
119      }
120      if (process.env.note === 'false') {
121        isShowNote = false;
122      }
123      printResult(buildPath);
124    });
125
126    compiler.hooks.compilation.tap('CommonAsset', compilation => {
127      compilation.hooks.processAssets.tap(
128        {
129          name: 'GLOBAL_COMMON_MODULE_CACHE',
130          stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
131        },
132        (assets) => {
133          if (assets['commons.js']) {
134            assets['commons.js'] = new CachedSource(
135              new ConcatSource(assets['commons.js'], GLOBAL_COMMON_MODULE_CACHE));
136          } else if (assets['vendors.js']) {
137            assets['vendors.js'] = new CachedSource(
138              new ConcatSource(assets['vendors.js'], GLOBAL_COMMON_MODULE_CACHE));
139          }
140        }
141      );
142    });
143
144    compiler.hooks.compilation.tap('Require', compilation => {
145      JavascriptModulesPlugin.getCompilationHooks(compilation).renderRequire.tap('renderRequire',
146        (source) => {
147          return process.env.DEVICE_LEVEL === 'rich' ? `var commonCachedModule =` +
148          ` globalThis["__common_module_cache__${process.env.hashProjectPath}"] ? ` +
149            `globalThis["__common_module_cache__${process.env.hashProjectPath}"]` +
150            `[moduleId]: null;\n` +
151            `if (commonCachedModule) { return commonCachedModule.exports; }\n` +
152            source.replace('// Execute the module function',
153            `function isCommonModue(moduleId) {
154              if (globalThis["webpackChunk${process.env.hashProjectPath}"]) {
155                const length = globalThis["webpackChunk${process.env.hashProjectPath}"].length;
156                switch (length) {
157                  case 1:
158                    return globalThis["webpackChunk${process.env.hashProjectPath}"][0][1][moduleId];
159                  case 2:
160                    return globalThis["webpackChunk${process.env.hashProjectPath}"][0][1][moduleId] ||
161                    globalThis["webpackChunk${process.env.hashProjectPath}"][1][1][moduleId];
162                }
163              }
164              return undefined;
165            }\n` +
166            `if (globalThis["__common_module_cache__${process.env.hashProjectPath}"]` +
167            ` && String(moduleId).indexOf("?name=") < 0 && isCommonModue(moduleId)) {\n` +
168              `  globalThis["__common_module_cache__${process.env.hashProjectPath}"]` +
169              `[moduleId] = module;\n}`) : source;
170        });
171    });
172  }
173}
174
175function addCacheFiles(entryFile, cachePath, entryPaths) {
176  const entryArray = [];
177  if (fs.existsSync(entryFile)) {
178    const oldArray = JSON.parse(fs.readFileSync(entryFile));
179    oldArray.forEach(element => {
180      entryPaths.add(element);
181    })
182  } else if (!(process.env.tddMode === 'true') && !(fs.existsSync(cachePath) && fs.statSync(cachePath).isDirectory())) {
183    mkDir(cachePath);
184  }
185  entryArray.push(...entryPaths);
186  if (fs.existsSync(cachePath) && fs.statSync(cachePath).isDirectory()) {
187    fs.writeFileSync(entryFile, JSON.stringify(entryArray));
188  }
189}
190
191const red = '\u001b[31m';
192const yellow = '\u001b[33m';
193const blue = '\u001b[34m';
194const reset = '\u001b[39m';
195
196const writeError = (buildPath, content) => {
197  fs.writeFile(path.resolve(buildPath, 'compile_error.log'), content, (err) => {
198    if (err) {
199      return console.error(err);
200    }
201  });
202};
203
204function printResult(buildPath) {
205  printWarning();
206  printError(buildPath);
207  if (errorCount + warningCount + noteCount > 0 || process.env.abcCompileSuccess === 'false') {
208    let result;
209    const resultInfo = {};
210    if (errorCount > 0) {
211      resultInfo.ERROR = errorCount;
212      result = 'FAIL ';
213    } else {
214      result = 'SUCCESS ';
215    }
216
217    if (process.env.abcCompileSuccess === 'false') {
218      result = 'FAIL ';
219    }
220
221    if (warningCount > 0) {
222      resultInfo.WARN = warningCount;
223    }
224
225    if (noteCount > 0) {
226      resultInfo.NOTE = noteCount;
227    }
228    if (result === 'SUCCESS ' && process.env.isPreview === 'true') {
229      printPreviewResult(resultInfo);
230    } else {
231      console.log(blue, 'COMPILE RESULT:' + result + JSON.stringify(resultInfo), reset);
232    }
233  } else {
234    if (process.env.isPreview === 'true') {
235      printPreviewResult();
236    } else {
237      console.log(blue, 'COMPILE RESULT:SUCCESS ', reset);
238    }
239  }
240  clearArkCompileStatus();
241}
242
243function clearArkCompileStatus() {
244  process.env.abcCompileSuccess = 'true';
245}
246
247function printPreviewResult(resultInfo = "") {
248  let workerNum = Object.keys(cluster.workers).length;
249  if (workerNum === 0) {
250    printSuccessInfo(resultInfo);
251  }
252}
253
254function printSuccessInfo(resultInfo) {
255  if (resultInfo.length === 0) {
256    console.log(blue, 'COMPILE RESULT:SUCCESS ', reset);
257  } else {
258    console.log(blue, 'COMPILE RESULT:SUCCESS ' + JSON.stringify(resultInfo), reset);
259  }
260}
261
262function printWarning() {
263  if (mWarningCount > 0) {
264    const warnings = mStats.compilation.warnings;
265    const length = warnings.length;
266    for (let index = 0; index < length; index++) {
267      let message = warnings[index].message
268      if (message.match(/noteStart(([\s\S])*)noteEnd/) !== null) {
269        noteCount++;
270        if (isShowNote) {
271          console.info(' ' + message.match(/noteStart(([\s\S])*)noteEnd/)[1].trim(), reset, '\n')
272        }
273      } else if (message.match(/warnStart(([\s\S])*)warnEnd/) !== null) {
274        warningCount++;
275        if (isShowWarning) {
276          console.warn(yellow, message.match(/warnStart(([\s\S])*)warnEnd/)[1].trim(), reset, '\n')
277        }
278      }
279    }
280    if (mWarningCount > length) {
281      warningCount = warningCount + mWarningCount - length;
282    }
283  }
284}
285
286function printError(buildPath) {
287  if (mErrorCount > 0) {
288    const errors = mStats.compilation.errors;
289    const length = errors.length;
290    if (isShowError) {
291      let errorContent = '';
292      for (let index = 0; index < length; index++) {
293        if (errors[index]) {
294          let message = errors[index].message
295          if (message) {
296            if (message.match(/errorStart(([\s\S])*)errorEnd/) !== null) {
297              const errorMessage = message.match(/errorStart(([\s\S])*)errorEnd/)[1];
298              console.error(red, errorMessage.trim(), reset, '\n');
299            } else {
300              const messageArrary = message.split('\n')
301              let logContent = ''
302              messageArrary.forEach(element => {
303                if (!(/^at/.test(element.trim()))) {
304                  logContent = logContent + element + '\n'
305                }
306              });
307              console.error(red, logContent, reset, '\n');
308            }
309            errorCount ++;
310            errorContent += message
311          }
312        }
313      }
314      writeError(buildPath, errorContent);
315    }
316  }
317}
318
319function writeUseOSFiles() {
320  let oldInfo = '';
321  if (!fs.existsSync(process.env.aceSoPath)) {
322    const parent = path.join(process.env.aceSoPath, '..');
323    if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) {
324      mkDir(parent);
325    }
326  } else {
327    oldInfo = fs.readFileSync(process.env.aceSoPath, 'utf-8') + '\n';
328  }
329  fs.writeFileSync(process.env.aceSoPath, oldInfo + Array.from(useOSFiles).join('\n'));
330}
331
332module.exports = ResultStates;
333