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}