1/* 2 * Copyright (c) 2020 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 path = require('path'); 17const fs = require('fs'); 18const CopyPlugin = require('copy-webpack-plugin'); 19const Webpack = require('webpack'); 20const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 21const { GenAbcPlugin } = require('./lib/gen_abc_plugin'); 22const buildPipeServer = require('./server/build_pipe_server'); 23 24const { 25 projectConfig, 26 loadEntryObj, 27 loadWorker, 28 abilityConfig, 29 readWorkerFile, 30 loadModuleInfo, 31 checkAppResourcePath, 32 addSDKBuildDependencies, 33 readPatchConfig, 34 getCleanConfig, 35 globalModulePaths 36} = require('./main'); 37const { ResultStates } = require('./lib/compile_info'); 38const { processUISyntax } = require('./lib/process_ui_syntax'); 39const { 40 IGNORE_ERROR_CODE, 41 removeDir, 42 mkdirsSync, 43 getResolveModules 44} = require('./lib/utils'); 45const { BUILD_SHARE_PATH, PREBUILDINFO_JSON } = require('./lib/pre_define'); 46process.env.watchMode = (process.env.watchMode && process.env.watchMode === 'true') || 'false'; 47 48function initConfig(config) { 49 const projectPath = path.resolve(projectConfig.projectPath); 50 Object.assign(config, { 51 entry: projectConfig.entryObj, 52 watch: process.env.watchMode === 'true', 53 watchOptions: { 54 aggregateTimeout: 10, 55 poll: false, 56 ignored: /node_modules/ 57 }, 58 output: { 59 path: path.resolve(__dirname, projectConfig.buildPath), 60 filename: '[name].js', 61 devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]', 62 globalObject: 'globalThis' 63 }, 64 devtool: 'nosources-source-map', 65 mode: 'development', 66 module: { 67 rules: [ 68 { 69 test: /\.d\.ts/, 70 use: [{ loader: path.resolve(__dirname, 'lib/process_dts_file.js') }] 71 }, 72 { 73 test: /(?<!\.d)\.(ets|ts)$/, 74 use: [ 75 { loader: path.resolve(__dirname, 'lib/result_process.js') }, 76 { 77 loader: 'ts-loader', 78 options: { 79 reportFiles: ['*.js'], 80 onlyCompileBundledFiles: true, 81 transpileOnly: true, 82 configFile: path.resolve(__dirname, 83 projectConfig.compileMode === 'esmodule' ? 'tsconfig.esm.json' : 'tsconfig.json'), 84 getCustomTransformers(program) { 85 let transformerOperation = { 86 before: [processUISyntax(program)], 87 after: [] 88 }; 89 90 return transformerOperation; 91 }, 92 ignoreDiagnostics: IGNORE_ERROR_CODE 93 } 94 }, 95 { loader: path.resolve(__dirname, 'lib/pre_process.js') } 96 ] 97 }, 98 setJsConfigRule() 99 ] 100 }, 101 node: { 102 global: false 103 }, 104 resolve: { 105 symlinks: projectConfig.compileMode === 'esmodule' ? false : true, 106 extensions: ['.js', '.ets', '.ts', '.d.ts', '.d.ets'], 107 modules: [ 108 projectPath, 109 './node_modules', 110 path.join(__dirname, 'node_modules'), 111 ...globalModulePaths 112 ] 113 }, 114 stats: { preset: 'none' }, 115 plugins: [ 116 new Webpack.WatchIgnorePlugin({ 117 paths: [ 118 /\.d\.ts$/ 119 ] 120 }), 121 new ResultStates() 122 ] 123 }); 124 setModuleJsonConfigRule(config); 125 if (!projectConfig.xtsMode) { 126 config.cache = { 127 type: "filesystem", 128 cacheDirectory: path.resolve(projectConfig.cachePath, '.ets_cache', 129 path.basename(projectConfig.projectPath)) 130 }; 131 } 132 if (!projectConfig.aceModuleJsonPath) { 133 config.resolve.modules.push(...getResolveModules(projectPath, true)); 134 existsPackageJson(config, path.resolve(projectPath, '../../../../../package.json'), 135 path.resolve(projectPath, '../../../../package.json')); 136 } else { 137 config.resolve.modules.push(...getResolveModules(projectPath, false)); 138 existsPackageJson(config, path.resolve(projectPath, '../../../../package.json'), 139 path.resolve(projectPath, '../../../package.json')); 140 } 141} 142 143function setJsConfigRule() { 144 const jsRule = { 145 test: /\.js$/, 146 use: [ 147 { loader: path.resolve(__dirname, 'lib/process_source_file.js') }, 148 { 149 loader: 'babel-loader', 150 options: { 151 plugins: ['@babel/plugin-proposal-class-properties'], 152 compact: false 153 } 154 }, 155 { loader: path.resolve(__dirname, 'lib/process_system_module.js') } 156 ] 157 }; 158 if (projectConfig.compileMode !== 'esmodule') { 159 jsRule.type = 'javascript/auto'; 160 jsRule.use[1].options.plugins.unshift([ 161 '@babel/plugin-transform-modules-commonjs', 162 { 163 'allowTopLevelThis': true 164 } 165 ]); 166 } else { 167 jsRule.resolve = { fullySpecified: false }; 168 } 169 if (projectConfig.compileHar) { 170 jsRule.use.unshift({ loader: path.resolve(__dirname, 'lib/process_har_writejs.js')}); 171 } 172 return jsRule; 173} 174 175function setModuleJsonConfigRule(config) { 176 if (projectConfig.compileMode !== 'esmodule') { 177 return; 178 } 179 const jsonRule = { 180 test: /\.json$/, 181 use: [ 182 { loader: path.resolve(__dirname, 'lib/process_source_file.js') } 183 ] 184 } 185 config.module.rules.push(jsonRule); 186} 187 188function existsPackageJson(config, rootPackageJsonPath, modulePackageJsonPath) { 189 if (config.cache) { 190 config.cache.buildDependencies = { 191 config: [] 192 }; 193 if (fs.existsSync(rootPackageJsonPath)) { 194 config.cache.buildDependencies.config.push(rootPackageJsonPath); 195 } 196 if (fs.existsSync(modulePackageJsonPath)) { 197 config.cache.buildDependencies.config.push(modulePackageJsonPath); 198 } 199 } 200} 201 202function setProjectConfig(envArgs) { 203 const args = Object.keys(envArgs); 204 if (args.indexOf('projectName') === args.length - 2) { 205 projectConfig.projectPath = path.join(process.cwd(), args[args.length - 1]); 206 } 207 if (envArgs.aceModuleRoot) { 208 projectConfig.projectPath = envArgs.aceModuleRoot; 209 } 210 if (envArgs.aceModuleBuild) { 211 projectConfig.buildPath = envArgs.aceModuleBuild; 212 } 213 if (envArgs.aceManifestPath) { 214 projectConfig.manifestFilePath = envArgs.aceManifestPath; 215 } 216 if (envArgs.aceProfilePath) { 217 projectConfig.aceProfilePath = envArgs.aceProfilePath; 218 } 219 if (envArgs.aceModuleJsonPath) { 220 projectConfig.aceModuleJsonPath = envArgs.aceModuleJsonPath; 221 } 222 if (envArgs.cachePath) { 223 projectConfig.cachePath = envArgs.cachePath; 224 } 225 if (envArgs.isPreview === "true") { 226 projectConfig.isPreview = true; 227 } 228} 229 230function setReleaseConfig(config) { 231 const TerserPlugin = require('terser-webpack-plugin'); 232 config.mode = 'production'; 233 if ((process.env.compileMode !== 'moduleJson' || projectConfig.splitCommon) && 234 abilityConfig.abilityType === 'page') { 235 config.optimization = config.optimization; 236 } else { 237 config.optimization = {}; 238 } 239 Object.assign(config.optimization, { 240 emitOnErrors: true, 241 usedExports: false, 242 minimize: true, 243 minimizer: [new TerserPlugin({ 244 terserOptions: { 245 compress: { 246 defaults: false, 247 dead_code: true, 248 collapse_vars: true, 249 unused: true, 250 drop_debugger: true, 251 if_return: true, 252 reduce_vars: true, 253 join_vars: false, 254 sequences: 0 255 }, 256 format: { 257 semicolons: false, 258 beautify: true, 259 braces: true, 260 indent_level: 2 261 } 262 } 263 })] 264 }); 265 config.output.devtoolModuleFilenameTemplate = (info) => { 266 return `webpack:///${info.absoluteResourcePath.replace(projectConfig.projectRootPath, '')}`; 267 }; 268 config.output.sourceMapFilename = '_releaseMap/[name].js.map'; 269 config.performance = { 270 hints: false 271 }; 272} 273 274function setCopyPluginConfig(config, appResource, isPreview) { 275 const copyPluginPattrens = []; 276 copyPluginPattrens.push({ 277 from: '**/*', 278 context: path.resolve(__dirname, projectConfig.projectPath), 279 globOptions: { 280 ignore: [ 281 '**/*.ets', 282 '**/*.ts', 283 '**/*.js', 284 path.resolve(__dirname, projectConfig.buildPath, '**').replace(/\\/g, '/') 285 ] 286 }, 287 to: path.resolve(__dirname, projectConfig.buildPath), 288 noErrorOnMissing: true 289 }); 290 const sharePath = path.resolve(__dirname, projectConfig.projectPath, BUILD_SHARE_PATH); 291 if (fs.existsSync(sharePath)) { 292 copyPluginPattrens.push({ 293 from: '**/*', 294 context: path.resolve(__dirname, projectConfig.projectPath, BUILD_SHARE_PATH), 295 to: path.resolve(__dirname, projectConfig.buildPath, BUILD_SHARE_PATH), 296 globOptions: { 297 ignore: [ 298 '**/*.ets', 299 '**/*.ts', 300 '**/*.js', 301 ] 302 }, 303 noErrorOnMissing: true 304 }); 305 } 306 if (abilityConfig.abilityType === 'page') { 307 if (fs.existsSync(projectConfig.manifestFilePath)) { 308 copyPluginPattrens.push({ 309 from: projectConfig.manifestFilePath, 310 to: path.resolve(__dirname, projectConfig.buildPath) 311 }); 312 } else if (fs.existsSync(projectConfig.aceConfigPath)) { 313 copyPluginPattrens.push({ 314 from: projectConfig.aceConfigPath, 315 to: path.resolve(__dirname, projectConfig.buildPath) 316 }); 317 } 318 } 319 if (appResource && fs.existsSync(appResource) && !projectConfig.xtsMode && 320 isPreview === 'true') { 321 copyPluginPattrens.push({ 322 from: path.resolve(__dirname, appResource), 323 to: path.resolve(__dirname, projectConfig.cachePath) 324 }); 325 } 326 config.plugins.push(new CopyPlugin({ patterns: copyPluginPattrens })); 327} 328 329function excludeWorker(workerFile, name) { 330 if (workerFile) { 331 return Object.keys(workerFile).includes(name); 332 } 333 return /^\.\/workers\//.test(name); 334} 335 336function setOptimizationConfig(config, workerFile) { 337 if ((process.env.compileMode !== 'moduleJson' || projectConfig.splitCommon) && 338 abilityConfig.abilityType === 'page') { 339 config.optimization = { 340 splitChunks: { 341 chunks(chunk) { 342 return !excludeWorker(workerFile, chunk.name) && !/^\.\/TestAbility/.test(chunk.name); 343 }, 344 minSize: 0, 345 cacheGroups: { 346 vendors: { 347 test: /[\\/]node_modules[\\/]/, 348 priority: -10, 349 name: "vendors", 350 }, 351 commons: { 352 name: 'commons', 353 priority: -20, 354 minChunks: 2, 355 reuseExistingChunk: true 356 } 357 } 358 } 359 } 360 } 361} 362 363function setGenAbcPlugin(env, config) { 364 process.env.compilerType = 'ark'; 365 process.env.panda = projectConfig.pandaMode; 366 let arkDir = path.join(__dirname, 'bin', 'ark'); 367 if (env.arkFrontendDir) { 368 arkDir = env.arkFrontendDir; 369 } 370 let nodeJs = 'node'; 371 if (env.nodeJs) { 372 nodeJs = env.nodeJs; 373 } 374 config.plugins.push(new GenAbcPlugin(projectConfig.buildPath, arkDir, nodeJs, 375 env.buildMode === 'debug')); 376 if (env.buildMode === 'release') { 377 config.output.path = path.join(projectConfig.cachePath, 'releaseAssets', 378 path.basename(projectConfig.buildPath)) 379 } 380} 381 382function setCleanWebpackPlugin(workerFile, config) { 383 config.plugins.push( 384 new CleanWebpackPlugin({ 385 dry: false, 386 dangerouslyAllowCleanPatternsOutsideProject: true, 387 cleanOnceBeforeBuildPatterns: getCleanConfig(workerFile) 388 }) 389 ); 390} 391 392function clearWebpackCacheByBuildInfo() { 393 if (!projectConfig.cachePath || projectConfig.xtsMode) { 394 return; 395 } 396 397 // clear&update cache dir when build info is different from last time 398 const cachePrebuildInfoPath = path.join(projectConfig.cachePath, PREBUILDINFO_JSON); 399 if (fs.existsSync(cachePrebuildInfoPath)) { 400 let cachedJson = undefined; 401 try { 402 cachedJson = JSON.parse(fs.readFileSync(cachePrebuildInfoPath).toString()); 403 } catch { 404 removeDir(projectConfig.cachePath); 405 mkdirsSync(projectConfig.cachePath); 406 } 407 if (projectConfig.compileMode === 'esmodule' && cachedJson && 408 (cachedJson.buildMode && cachedJson.buildMode !== projectConfig.buildArkMode || 409 cachedJson.bundleName && cachedJson.bundleName !== projectConfig.bundleName || 410 cachedJson.moduleName && cachedJson.moduleName !== projectConfig.moduleName)) { 411 removeDir(projectConfig.cachePath); 412 mkdirsSync(projectConfig.cachePath); 413 } 414 // api version is 8 or 9 415 if (projectConfig.compileMode === 'jsbundle' && cachedJson && 416 cachedJson.minAPIVersion && cachedJson.minAPIVersion.toString() !== process.env.minPlatformVersion) { 417 removeDir(projectConfig.cachePath); 418 mkdirsSync(projectConfig.cachePath); 419 } 420 } 421} 422 423module.exports = (env, argv) => { 424 const config = {}; 425 setProjectConfig(env); 426 loadEntryObj(projectConfig); 427 loadModuleInfo(projectConfig, env); 428 clearWebpackCacheByBuildInfo(); 429 initConfig(config); 430 readPatchConfig(); 431 const workerFile = readWorkerFile(); 432 setOptimizationConfig(config, workerFile); 433 setCleanWebpackPlugin(workerFile, config); 434 435 if (env.isPreview !== "true") { 436 loadWorker(projectConfig, workerFile); 437 if (env.compilerType && env.compilerType === 'ark' && !projectConfig.compileHar) { 438 setGenAbcPlugin(env, config); 439 } 440 } else { 441 projectConfig.isPreview = true; 442 projectConfig.checkEntry = env.checkEntry; 443 setGenAbcPlugin(env, config); 444 let port; 445 process.argv.forEach((val, index) => { 446 if (val.startsWith('port=')) { 447 port = val.split('=')[1]; 448 } 449 }); 450 if (port) { 451 buildPipeServer.init(port); 452 } 453 } 454 455 if (projectConfig.compileHar && env.obfuscate === 'uglify') { 456 projectConfig.obfuscateHarType = 'uglify'; 457 } 458 459 if (env.sourceMap === 'none') { 460 config.devtool = false; 461 } 462 463 if (env.buildMode === 'release' && projectConfig.compileMode !== 'esmodule') { 464 setReleaseConfig(config); 465 } 466 467 if (projectConfig.compileMode === 'esmodule' && projectConfig.harNameOhmMap) { 468 config.externals = []; 469 for (const harName in projectConfig.harNameOhmMap) { 470 config.externals.push(RegExp('^(' + harName + ')($|\\/\\S+)')); 471 } 472 } 473 474 const appResourcePath = env.appResource || process.env.appResource; 475 checkAppResourcePath(appResourcePath, config); 476 setCopyPluginConfig(config, appResourcePath, env.isPreview); 477 addSDKBuildDependencies(config); 478 config.output.library = projectConfig.hashProjectPath; 479 return config; 480} 481