• 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  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