• 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');
18const cluster = require('cluster');
19const process = require('process');
20const os = require('os');
21
22const forward = '(global.___mainEntry___ = function (globalObjects) {' + '\n' +
23              '  var define = globalObjects.define;' + '\n' +
24              '  var require = globalObjects.require;' + '\n' +
25              '  var bootstrap = globalObjects.bootstrap;' + '\n' +
26              '  var register = globalObjects.register;' + '\n' +
27              '  var render = globalObjects.render;' + '\n' +
28              '  var $app_define$ = globalObjects.$app_define$;' + '\n' +
29              '  var $app_bootstrap$ = globalObjects.$app_bootstrap$;' + '\n' +
30              '  var $app_require$ = globalObjects.$app_require$;' + '\n' +
31              '  var history = globalObjects.history;' + '\n' +
32              '  var Image = globalObjects.Image;' + '\n' +
33              '  var OffscreenCanvas = globalObjects.OffscreenCanvas;' + '\n' +
34              '  (function(global) {' + '\n' +
35              '    "use strict";' + '\n';
36const last = '\n' + '})(this.__appProto__);' + '\n' + '})';
37const genAbcScript = 'gen-abc.js';
38const NODE_MODULES = 'node_modules';
39const SUCCESS = 0;
40const FAIL = 1;
41const red = '\u001b[31m';
42const reset = '\u001b[39m';
43const WINDOWS = 'Windows_NT';
44const LINUX = 'Linux';
45const MAC = 'Darwin';
46let output;
47let isWin = false;
48let isMac = false;
49let isDebug = false;
50let arkDir;
51let nodeJs;
52let intermediateJsBundle = [];
53let workerFile = null;
54
55class GenAbcPlugin {
56  constructor(output_, arkDir_, nodeJs_, workerFile_, isDebug_) {
57    output = output_;
58    arkDir = arkDir_;
59    nodeJs = nodeJs_;
60    isDebug = isDebug_;
61    workerFile = workerFile_;
62  }
63  apply(compiler) {
64    if (fs.existsSync(path.resolve(arkDir, 'build-win'))) {
65      isWin = true;
66    } else if (fs.existsSync(path.resolve(arkDir, 'build-mac'))) {
67      isMac = true;
68    } else if (!fs.existsSync(path.resolve(arkDir, 'build'))) {
69      return;
70    }
71
72    if (!checkNodeModules()) {
73      process.exitCode = FAIL;
74      return;
75    }
76
77    compiler.hooks.emit.tap('GenAbcPlugin', (compilation) => {
78      const assets = compilation.assets;
79      const keys = Object.keys(assets);
80      keys.forEach(key => {
81        // choice *.js
82        if (output && path.extname(key) === '.js') {
83          let newContent = assets[key].source();
84          if (checkWorksFile(key, workerFile) && key !== 'commons.js' && key !== 'vendors.js') {
85            newContent = forward + newContent + last;
86          }
87          if (key === 'commons.js' || key === 'vendors.js' || !checkWorksFile(key, workerFile)) {
88            newContent = `\n\n\n\n\n\n\n\n\n\n\n\n\n\n` + newContent;
89          }
90          const keyPath = key.replace(/\.js$/, ".temp.js");
91          writeFileSync(newContent, path.resolve(output, keyPath), true);
92        } else if (output && path.extname(key) === '.json' &&
93          process.env.DEVICE_LEVEL === 'card' && process.env.configOutput && !checkI18n(key)) {
94          writeFileSync(assets[key].source(), path.resolve(output, key), false);
95        }
96      })
97    });
98    compiler.hooks.afterEmit.tap('GenAbcPluginMultiThread', () => {
99      if (intermediateJsBundle.length === 0) {
100        return;
101      }
102      invokeWorkerToGenAbc();
103    });
104  }
105}
106
107function checkI18n(key) {
108  const outI18nPath = path.resolve(process.env.configOutput, key);
109  const projectI18nPath = outI18nPath.replace(output, process.env.projectPath);
110  if (projectI18nPath.indexOf(
111    path.resolve(__dirname, process.env.projectPath, 'i18n')) > -1) {
112    return true;
113  }
114  return false;
115}
116
117function checkWorksFile(assetPath, workerFile) {
118  if (workerFile === null) {
119    if (assetPath.search("./workers/") !== 0) {
120      return true;
121    } else {
122      return false;
123    }
124  } else {
125    for (const key in workerFile) {
126      let keyExt = key + '.js';
127      if (keyExt === assetPath) {
128        return false;
129      }
130    }
131  }
132
133  return true;
134}
135
136function toUnixPath(data) {
137  if (/^win/.test(require('os').platform())) {
138    const fileTmps= data.split(path.sep);
139    const newData = path.posix.join(...fileTmps);
140    return newData;
141  }
142  return data;
143}
144
145function writeFileSync(inputString, output, isToBin) {
146    validateFilePathLength(output);
147    const parent = path.join(output, '..');
148    if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) {
149        mkDir(parent);
150    }
151    fs.writeFileSync(output, inputString);
152    if (!isToBin) {
153      return;
154    }
155    if (fs.existsSync(output)) {
156      output = toUnixPath(output);
157      let fileSize = fs.statSync(output).size;
158      intermediateJsBundle.push({path: output, size: fileSize});
159    } else {
160      console.debug(red, `ETS:ERROR Failed to convert file ${input} to abc,  output is lost`, reset);
161      process.exitCode = FAIL;
162    }
163}
164
165function mkDir(path_) {
166    const parent = path.join(path_, '..');
167    if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) {
168        mkDir(parent);
169    }
170    fs.mkdirSync(path_);
171}
172
173function getSmallestSizeGroup(groupSize) {
174  let groupSizeArray = Array.from(groupSize);
175  groupSizeArray.sort(function(g1, g2) {
176    return g1[1] - g2[1]; // sort by size
177  });
178  return groupSizeArray[0][0];
179}
180
181function splitJsBundlesBySize(bundleArray, groupNumber) {
182  let result = [];
183  if (bundleArray.length < groupNumber) {
184    result.push(bundleArray);
185    return result;
186  }
187
188  bundleArray.sort(function(f1, f2) {
189    return f2.size - f1.size;
190  });
191  let groupFileSize = new Map();
192  for (let i = 0; i < groupNumber; ++i) {
193    result.push([]);
194    groupFileSize.set(i, 0);
195  }
196
197  let index = 0;
198  while(index < bundleArray.length) {
199    let smallestGroup = getSmallestSizeGroup(groupFileSize);
200    result[smallestGroup].push(bundleArray[index]);
201    let sizeUpdate = groupFileSize.get(smallestGroup) + bundleArray[index].size;
202    groupFileSize.set(smallestGroup, sizeUpdate);
203    index++;
204  }
205  return result;
206}
207
208function invokeWorkerToGenAbc() {
209  let param = '';
210  if (isDebug) {
211    param += ' --debug';
212  }
213
214  let js2abc = path.join(arkDir, 'build', 'src', 'index.js');
215  if (isWin) {
216    js2abc = path.join(arkDir, 'build-win', 'src', 'index.js');
217  } else if (isMac) {
218    js2abc = path.join(arkDir, 'build-mac', 'src', 'index.js');
219  }
220  validateFilePathLength(js2abc);
221
222  const maxWorkerNumber = 3;
223  const splitedBundles = splitJsBundlesBySize(intermediateJsBundle, maxWorkerNumber);
224  const workerNumber = maxWorkerNumber < splitedBundles.length ? maxWorkerNumber : splitedBundles.length;
225  const cmdPrefix = `${nodeJs} --expose-gc "${js2abc}" ${param} `;
226
227  const clusterNewApiVersion = 16;
228  const currentNodeVersion = parseInt(process.version.split('.')[0]);
229  const useNewApi = currentNodeVersion >= clusterNewApiVersion ? true : false;
230
231  if ((useNewApi && cluster.isPrimary) || (!useNewApi && cluster.isMaster)) {
232    if (useNewApi) {
233      cluster.setupPrimary({
234        exec: path.resolve(__dirname, genAbcScript)
235      });
236    } else {
237      cluster.setupMaster({
238        exec: path.resolve(__dirname, genAbcScript)
239      });
240    }
241
242    for (let i = 0; i < workerNumber; ++i) {
243      let workerData = {
244        "inputs": JSON.stringify(splitedBundles[i]),
245        "cmd": cmdPrefix
246      }
247      cluster.fork(workerData);
248    }
249
250    cluster.on('exit', (worker, code, signal) => {
251      if (code == FAIL || process.exitCode ===  FAIL) {
252        process.exitCode = FAIL;
253        return;
254      }
255    });
256
257    process.on('exit', (code) => {
258      intermediateJsBundle.forEach((item) => {
259        let input = item.path;
260        if (fs.existsSync(input)) {
261          fs.unlinkSync(input);
262        }
263      })
264    });
265  }
266}
267
268module.exports = {
269  GenAbcPlugin: GenAbcPlugin,
270  checkWorksFile: checkWorksFile
271}
272
273function checkNodeModules() {
274  let arkEntryPath = path.join(arkDir, 'build');
275  if (isWin) {
276    arkEntryPath = path.join(arkDir, 'build-win');
277  } else if (isMac) {
278    arkEntryPath = path.join(arkDir, 'build-mac');
279  }
280  let nodeModulesPath = path.join(arkEntryPath, NODE_MODULES);
281  validateFilePathLength(nodeModulesPath);
282  if (!(fs.existsSync(nodeModulesPath) && fs.statSync(nodeModulesPath).isDirectory())) {
283    console.error(red, `ERROR: node_modules for ark compiler not found.
284      Please make sure switch to non-root user before runing "npm install" for safity requirements and try re-run "npm install" under ${arkEntryPath}`, reset);
285    return false;
286  }
287
288  return true;
289}
290
291export function isWindows() {
292  return os.type() === WINDOWS;
293}
294
295export function isLinux() {
296  return os.type() === LINUX;
297}
298
299export function isMacOs() {
300  return os.type() === MAC;
301}
302
303export function maxFilePathLength() {
304  if (isWindows()) {
305    return 32766;
306  } else if (isLinux()) {
307    return 4095;
308  } else if (isMacOs()) {
309    return 1016;
310  } else {
311    return -1;
312  }
313}
314
315export function validateFilePathLength(filePath) {
316  if (maxFilePathLength() < 0) {
317    console.error("Unknown OS platform");
318    process.exitCode = FAIL;
319    return false;
320  } else if (filePath.length > 0 && filePath.length <= maxFilePathLength()) {
321    return true;
322  } else if (filePath.length > maxFilePathLength()) {
323    console.error(`The length of ${filePath} exceeds the limitation of current platform, which is ` +
324    `${maxFilePathLength()}. Please try moving the project folder to avoid deeply nested file path and try again`);
325    process.exitCode = FAIL;
326    return false;
327  } else {
328    console.error("Validate file path failed");
329    process.exitCode = FAIL;
330    return false;
331  }
332}