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