• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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