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
16var path = require('path')
17var fs = require('fs')
18
19var ResourcePlugin = require('./lib/resource-plugin')
20var ResultStates = require('./lib/compile-plugin')
21var GenBinPlugin = require('./lib/genBin-plugin')
22var GenAbcPlugin = require('./lib/genAbc-plugin').GenAbcPlugin
23var AfterEmitPlugin = require('./lib/cardJson-plugin').AfterEmitPlugin
24const ReadJsonPlugin = require('./lib/read-json-plugin')
25
26const { PLATFORM }= require('./lib/lite/lite-enum')
27const util = require('./lib/util')
28const TerserPlugin = require('terser-webpack-plugin')
29const CopyPlugin = require("copy-webpack-plugin")
30const webpack = require('webpack')
31let watchMode = (process.env.watchMode && process.env.watchMode === 'true') || false
32const {
33  deleteFolderRecursive,
34  readManifest,
35  loadEntryObj,
36  hashProjectPath,
37  checkMultiResourceBuild,
38  readWorkerFile,
39  compareCache,
40  parseAbilityName
41} = require('./main.product')
42
43const richModule = {
44  rules: [
45    {
46      test: /\.visual$/,
47      use: [{
48        loader: path.resolve(__dirname, './lib/loader-gen.js')
49      }]
50    },
51    {
52      test: /(\.hml)(\?[^?]+)?$/,
53      use: [{
54        loader: path.resolve(__dirname, './index.js')
55      }]
56    },
57    {
58      test: /\.png$/,
59      use: [{
60        loader: 'file-loader',
61        options: {
62          name: '[name].[ext]',
63          outputPath: 'common'
64        }
65      }]
66    },
67    {
68      test: /\.css$/,
69      use: [{
70        loader: 'css-loader'
71      }]
72    },
73    {
74      test: /\.less$/,
75      use: [{
76        loader: 'less-loader'
77      }]
78    },
79    {
80      test: /\.(scss|sass)$/,
81      use: [{
82        loader: 'style-loader!css-loader!sass-loader'
83      }]
84    },
85    {
86      test: /\.jsx?$/,
87      type: 'javascript/auto',
88      use: [
89        {
90          loader: path.resolve(__dirname, 'lib/module-script.js')
91        },
92        {
93          loader: util.loadBabelModule('babel-loader'),
94          options: {
95            presets: [util.loadBabelModule('@babel/preset-env')],
96            targets: 'node 8',
97            plugins: [
98              [
99                util.loadBabelModule('@babel/plugin-transform-modules-commonjs'),
100                {'allowTopLevelThis': true}
101              ],
102              util.loadBabelModule('@babel/plugin-proposal-class-properties')
103            ],
104            compact: false
105          }
106        }
107      ]
108    }
109  ]
110}
111
112const cardModule = {
113  rules: [
114    {
115      test: /\.visual$/,
116      use: [{
117        loader: path.resolve(__dirname, './lib/loader-gen.js')
118      }]
119    },
120    {
121      test: /\.hml$/,
122      use: [{
123        loader: path.resolve(__dirname, './lib/card-loader.js')
124      }]
125    },
126    {
127      test: /\.css$/,
128      use: [{
129        loader: 'css-loader'
130      }]
131    },
132    {
133      test: /\.less$/,
134      use: [{
135        loader: 'less-loader'
136      }]
137    },
138    {
139      test: /\.(scss|sass)$/,
140      use: [{
141        loader: 'css-loader!sass-loader'
142      }]
143    },
144    {
145      test: /\.jsx?$/,
146      type: 'javascript/auto',
147      use: [
148        {
149          loader: path.resolve(__dirname, 'lib/module-script.js')
150        },
151        {
152          loader: util.loadBabelModule('babel-loader'),
153          options: {
154            presets: [util.loadBabelModule('@babel/preset-env')],
155            targets: 'node 8',
156            plugins: [util.loadBabelModule('@babel/plugin-transform-modules-commonjs'),
157            util.loadBabelModule('@babel/plugin-proposal-class-properties')],
158            compact: false
159          }
160        }
161      ]
162    }
163  ]
164}
165
166let config = {
167  cache: {
168    type: 'filesystem'
169  },
170  watch: watchMode,
171  watchOptions: {
172    aggregateTimeout: 10,
173    poll: false,
174    ignored: ["**/node_modules", "**/oh_modules", "**/*.json~"]
175  },
176  optimization: {
177    moduleIds: 'deterministic',
178    chunkIds: 'deterministic',
179  },
180  output: {
181    filename: '[name].js',
182    pathinfo: false,
183    devtoolModuleFilenameTemplate: (info) => {
184      const newInfo = info.absoluteResourcePath.replace(process.env.projectRootPath + path.sep, '')
185        .replace(process.env.projectRootPath + path.sep, '')
186        .replace(path.join(__dirname, path.sep), '');
187      return newInfo;
188    },
189    globalObject: 'globalThis'
190  },
191  devtool: 'nosources-source-map',
192  mode: 'development',
193  module: richModule,
194  node: {
195    global: false
196  },
197  stats: 'none'
198}
199
200function setConfigs(env) {
201  if (process.env.aceModuleJsonPath || env.aceModuleJsonPath) {
202    process.env.compileMode = 'moduleJson';
203  }
204  process.env.error = env.error === undefined ? true : env.error
205  process.env.warning = env.warning === undefined ? true : env.warning
206  process.env.note = env.note === undefined ? true : env.note
207  process.env.buildMode = env.buildMode || 'debug'
208  process.env.logLevel = env.logLevel || '1'
209  process.env.isPreview = env.isPreview || false
210  process.env.projectPath = env.aceModuleRoot || process.env.aceModuleRoot || process.cwd();
211  hashProjectPath(process.env.projectPath);
212  process.env.buildPath = env.aceModuleBuild || process.env.aceModuleBuild ||
213    path.resolve(process.env.projectPath, 'build');
214  process.env.cachePath = env.cachePath || process.env.cachePath || path.resolve(__dirname, 'node_modules/.cache');
215  process.env.aceManifestPath = process.env.aceManifestPath || path.resolve(process.env.projectPath, 'manifest.json');
216  process.env.abilityType = process.env.abilityType || 'page';
217  process.env.DEVICE_LEVEL = env.DEVICE_LEVEL || process.env.DEVICE_LEVEL || 'rich';
218  process.env.aceModuleJsonPath = env.aceModuleJsonPath || process.env.aceModuleJsonPath;
219  process.env.aceProfilePath = env.aceProfilePath || process.env.aceProfilePath;
220  process.env.watchCSSFiles = process.env.watchCSSFiles || path.resolve(process.env.cachePath, '.rich_cache', 'preview_css.json');
221  watchMode = (process.env.watchMode && process.env.watchMode === 'true') ||
222    (env.watchMode && env.watchMode === 'true') || false;
223  if (process.env.abilityType === 'page' || process.env.abilityType === 'form') {
224    const manifest = readManifest(process.env.aceManifestPath)
225    if (process.env.compileMode !== 'moduleJson') {
226      process.env.DEVICE_LEVEL = manifest.type === 'form' ? 'card' : 'rich'
227    }
228    process.env.PLATFORM_VERSION = PLATFORM.VERSION6;
229    const version = parseInt(manifest.minPlatformVersion);
230    if (version == 5) {
231      process.env.PLATFORM_VERSION = PLATFORM.VERSION5;
232    }
233    if (version <= 4) {
234      process.env.PLATFORM_VERSION = PLATFORM.VERSION3;
235    }
236  }
237  process.env.aceBuildJson = env.aceBuildJson || process.env.aceBuildJson;
238  checkMultiResourceBuild(process.env.aceBuildJson);
239}
240
241function setArkPlugin(env, workerFile) {
242  if (env.isPreview === "true" || env.compilerType && env.compilerType === 'ark') {
243    let arkDir = path.join(__dirname, 'bin', 'ark');
244    if (env.arkFrontendDir) {
245      arkDir = env.arkFrontendDir;
246    }
247    let nodeJs = 'node';
248    if (env.nodeJs) {
249      nodeJs = env.nodeJs;
250    }
251    config.plugins.push(new GenAbcPlugin(process.env.buildPath, arkDir, nodeJs, workerFile,
252      env.buildMode === 'debug'))
253    if (env.buildMode === 'release') {
254      config.output.path = path.join(process.env.cachePath, "releaseAssets",
255        path.basename(process.env.buildPath));
256      process.env.configOutput = config.output.path;
257    }
258  } else {
259    if (env.deviceType) {
260      let deviceArr = env.deviceType.split(/,/)
261      let isDefault = deviceArr.indexOf('tv') >= 0 || deviceArr.indexOf('wearable') >= 0 ? true : false
262      if (isDefault) {
263        config.plugins.push(new GenBinPlugin(process.env.buildPath, path.join(__dirname, 'bin', workerFile)))
264      }
265    }
266  }
267}
268
269function existsPackageJson(config, rootPackageJsonPath, modulePackageJsonPath) {
270  if (config.cache) {
271    config.cache.buildDependencies = {
272      config: []
273    };
274    if (fs.existsSync(rootPackageJsonPath)) {
275      config.cache.buildDependencies.config.push(rootPackageJsonPath);
276    }
277    if (fs.existsSync(modulePackageJsonPath)) {
278      config.cache.buildDependencies.config.push(modulePackageJsonPath);
279    }
280  }
281}
282
283function excludeWorker(workerFile, name) {
284  if (workerFile) {
285    return Object.keys(workerFile).includes(name);
286  }
287  return /^\.\/workers\//.test(name);
288}
289
290module.exports = (env) => {
291  setConfigs(env);
292  compareCache(path.resolve(process.env.cachePath, '.rich_cache'));
293  const workerFile = readWorkerFile();
294  if (process.env.compileMode === 'moduleJson') {
295    process.env.DEVICE_LEVEL = 'card';
296    config.entry = {};
297  } else {
298    deleteFolderRecursive(process.env.buildPath);
299    config.entry = loadEntryObj(process.env.projectPath, process.env.DEVICE_LEVEL,
300      process.env.abilityType, process.env.aceManifestPath);
301    existsPackageJson(config, path.resolve(process.env.projectPath, '../../../../../package.json'),
302      path.resolve(process.env.projectPath, '../../../../package.json'));
303  }
304  config.cache.cacheDirectory = path.resolve(process.env.cachePath, '.rich_cache',
305    path.basename(process.env.projectPath));
306  config.output.path = path.resolve(__dirname, process.env.buildPath)
307  config.plugins = [
308    new ResourcePlugin(process.env.projectPath, process.env.buildPath,
309      process.env.aceManifestPath, process.env.watchCSSFiles, workerFile),
310    new ResultStates({
311      build: process.env.buildPath
312    }),
313    new webpack.DefinePlugin({
314      STANDARD: JSON.stringify(true),
315      LITE: JSON.stringify(false)
316    })
317  ]
318  config.resolve = {
319    modules: [
320      process.env.projectPath,
321      path.join(process.env.projectPath, '../../../../../'),
322      path.join(__dirname, 'node_modules'),
323      './node_modules',
324      './oh_modules'
325    ],
326    descriptionFiles: ['package.json', 'oh-package.json5'],
327    plugins: [new ReadJsonPlugin()],
328  }
329  if (fs.existsSync(path.resolve(process.env.projectPath, 'i18n'))) {
330    config.plugins.push(new CopyPlugin({
331      patterns: [
332        {
333          from: path.resolve(process.env.projectPath, 'i18n'),
334          to: path.resolve(process.env.buildPath, 'i18n'),
335          noErrorOnMissing: true
336        }
337      ]
338    }))
339  }
340  if (process.env.aceConfigPath && fs.existsSync(process.env.aceConfigPath)) {
341    config.plugins.push(new CopyPlugin({
342      patterns: [
343        {
344          from: path.resolve(process.env.aceConfigPath),
345          to: path.resolve(process.env.buildPath, 'config.json'),
346          noErrorOnMissing: true
347        }
348      ]
349    }))
350  }
351  if (process.env.DEVICE_LEVEL === 'card') {
352    config.module = cardModule
353    config.plugins.push(new AfterEmitPlugin())
354    config.optimization = {};
355    setArkPlugin(env, workerFile);
356  } else {
357    if (process.env.compileMode !== 'moduleJson' && process.env.abilityType === 'page') {
358      Object.assign(config.optimization, {
359        splitChunks: {
360          chunks(chunk) {
361            return !excludeWorker(workerFile, chunk.name) && !/^\.\/TestAbility/.test(chunk.name);
362          },
363          minSize: 0,
364          cacheGroups: {
365            vendors: {
366              test: /[\\/](node|oh)_modules[\\/]/,
367              priority: 20,
368              name: "vendors",
369            },
370            commons: {
371              test: /\.js|css|hml$/,
372              name: 'commons',
373              priority: 10,
374              minChunks: 2,
375            }
376          }
377        },
378      });
379    }
380    setArkPlugin(env, workerFile);
381    if (env.sourceMap === 'none') {
382      config.devtool = false
383    }
384    if (env.buildMode === 'release') {
385      config.mode = 'production';
386      Object.assign(config.optimization, {
387        minimize: true,
388        minimizer: [new TerserPlugin({
389          terserOptions: {
390            compress: {
391              defaults: false,
392              dead_code: true,
393              collapse_vars: true,
394              unused: true,
395              drop_debugger: true,
396              if_return: true,
397              reduce_vars: true,
398              join_vars: false,
399              sequences: 0,
400            },
401            format: {
402              semicolons: false,
403              beautify: true,
404              braces: true,
405              indent_level: 2,
406            },
407          },
408        })],
409      });
410      config.output.devtoolModuleFilenameTemplate = (info) => {
411        const newInfo = info.absoluteResourcePath.replace(process.env.projectRootPath + path.sep, '')
412          .replace(process.env.projectRootPath + path.sep, '')
413          .replace(path.join(__dirname, path.sep), '');
414        return newInfo;
415      }
416      config.output.sourceMapFilename = '_releaseMap/[name].js.map'
417    }
418  }
419  if (process.env.abilityType === 'testrunner') {
420    config.module.rules = [];
421    config.module.rules.unshift({
422      test: /TestRunner/,
423      use: [{
424        loader: path.resolve(__dirname, './index.js')
425      }]
426    })
427  } else {
428    config.module.rules.unshift({
429      test: parseAbilityName(process.env.abilityType, process.env.projectPath),
430      use: [{
431        loader: path.resolve(__dirname, './index.js')
432      }]
433    })
434  }
435
436  config.output.library = process.env.hashProjectPath;
437  return config
438}
439