/ webpack_config / makeConfig.js
makeConfig.js
  1  'use strict';
  2  const path = require('path');
  3  const webpack = require('webpack');
  4  const threadLoader = require('thread-loader');
  5  
  6  const HtmlWebpackPlugin = require('html-webpack-plugin');
  7  const CopyWebpackPlugin = require('copy-webpack-plugin');
  8  const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
  9  const WebappWebpackPlugin = require('webapp-webpack-plugin');
 10  // const AutoDllPlugin = require('autodll-webpack-plugin');
 11  const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
 12  const ProgressPlugin = require('webpack/lib/ProgressPlugin');
 13  const SriPlugin = require('webpack-subresource-integrity');
 14  const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
 15  const ClearDistPlugin = require('./plugins/clearDist');
 16  
 17  const config = require('./config');
 18  
 19  const DEFAULT_OPTIONS = {
 20    isProduction: false,
 21    isElectronBuild: false,
 22    isHTMLBuild: false,
 23    outputDir: ''
 24  };
 25  
 26  module.exports = function(opts = {}) {
 27    const options = Object.assign({}, DEFAULT_OPTIONS, opts);
 28    const isDownloadable = options.isHTMLBuild || options.isElectronBuild;
 29    const commitHash = process.env.npm_package_gitHead;
 30  
 31    // ====================
 32    // ====== Entry =======
 33    // ====================
 34    const entry = {
 35      badBrowserCheckA: './common/badBrowserCheckA.js',
 36      badBrowserCheckB: './common/badBrowserCheckB.js',
 37      client: './common/index.tsx'
 38    };
 39  
 40    if (options.isProduction) {
 41      entry.vendor = config.vendorModules;
 42    }
 43  
 44    // ====================
 45    // ====== Rules =======
 46    // ====================
 47    const rules = [];
 48  
 49    // Typescript
 50    if (options.isProduction || !process.env.SLOW_BUILD_SPEED) {
 51      rules.push(config.typescriptRule);
 52    } else {
 53      threadLoader.warmup(config.typescriptRule.use[0].options, [
 54        config.typescriptRule.use[0].loader
 55      ]);
 56      rules.push({
 57        ...config.typescriptRule,
 58        use: [
 59          {
 60            loader: 'thread-loader',
 61            options: {
 62              workers: 4
 63            }
 64          },
 65          ...config.typescriptRule.use
 66        ]
 67      });
 68    }
 69  
 70    // Styles (CSS, SCSS)
 71    const sassLoader = {
 72      loader: 'sass-loader',
 73      options: {
 74        data: `$is-electron: ${options.isElectronBuild};`
 75      }
 76    };
 77  
 78    if (options.isProduction) {
 79      rules.push(
 80        {
 81          test: /\.css$/,
 82          use: [MiniCSSExtractPlugin.loader, 'css-loader']
 83        },
 84        {
 85          test: /\.scss$/,
 86          use: [MiniCSSExtractPlugin.loader, 'css-loader', sassLoader]
 87        }
 88      );
 89    } else {
 90      rules.push(
 91        {
 92          test: /\.css$/,
 93          include: path.resolve(config.path.src, 'vendor'),
 94          use: ['style-loader', 'css-loader']
 95        },
 96        {
 97          test: /\.scss$/,
 98          include: ['components', 'containers', 'sass']
 99            .map(dir => path.resolve(config.path.src, dir))
100            .concat([config.path.modules]),
101  
102          use: ['style-loader', 'css-loader', sassLoader]
103        }
104      );
105    }
106  
107    // Web workers
108    rules.push({
109      test: /\.worker\.js$/,
110      loader: 'worker-loader'
111    });
112  
113    // Images
114    rules.push({
115      include: [path.resolve(config.path.assets), path.resolve(config.path.modules)],
116      test: /\.(gif|png|jpe?g|svg)$/i,
117      use: [
118        {
119          loader: 'file-loader',
120          options: {
121            hash: 'sha512',
122            digest: 'hex',
123            name: '[path][name].[ext]?[hash:6]'
124          }
125        },
126        {
127          loader: 'image-webpack-loader',
128          options: {
129            bypassOnDebug: true,
130            optipng: {
131              optimizationLevel: 4
132            },
133            gifsicle: {
134              interlaced: false
135            },
136            mozjpeg: {
137              quality: 80
138            },
139            svgo: {
140              plugins: [{ removeViewBox: true }, { removeEmptyAttrs: false }, { sortAttrs: true }]
141            }
142          }
143        }
144      ]
145    });
146  
147    // Fonts
148    rules.push({
149      include: [path.resolve(config.path.assets), path.resolve(config.path.modules)],
150      test: /\.(ico|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/,
151      loader: 'file-loader'
152    });
153  
154    // ====================
155    // ====== Plugins =====
156    // ====================
157    const plugins = [
158      new HtmlWebpackPlugin({
159        template: path.resolve(config.path.src, 'index.html'),
160        inject: true,
161        title: config.title,
162        appDescription: config.description,
163        appUrl: config.url,
164        image: config.img,
165        type: config.type,
166        twitter: {
167          site: config.twitter.creator,
168          creator: config.twitter.creator
169        },
170        metaCsp: options.isProduction 
171          ? "default-src 'none'; script-src 'self'; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; manifest-src 'self'; font-src 'self'; img-src 'self' data: https://shapeshift.io; connect-src *;"
172          :  ""
173      }),
174  
175      new CopyWebpackPlugin([
176        {
177          from: config.path.static,
178          // to the root of dist path
179          to: './'
180        },
181        {
182          from: path.resolve(config.path.assets, 'images/link-preview.png'),
183          to: './common/assets/images'
184        }
185      ]),
186  
187      new webpack.LoaderOptionsPlugin({
188        minimize: options.isProduction,
189        debug: !options.isProduction,
190        options: {
191          // css-loader relies on context
192          context: process.cwd()
193        }
194      }),
195  
196      new webpack.DefinePlugin({
197        'process.env.NODE_ENV': JSON.stringify(options.isProduction ? 'production' : 'development'),
198        'process.env.BUILD_DOWNLOADABLE': JSON.stringify(isDownloadable),
199        'process.env.BUILD_HTML': JSON.stringify(options.isHTMLBuild),
200        'process.env.BUILD_ELECTRON': JSON.stringify(options.isElectronBuild)
201      })
202    ];
203  
204    if (options.isProduction) {
205      plugins.push(
206        new MiniCSSExtractPlugin({
207          filename: `[name].[contenthash:8].css`
208        }),
209        new WebappWebpackPlugin({
210          logo: path.resolve(config.path.assets, 'images/favicon.png'),
211          cacheDirectory: false, // Cache makes builds nondeterministic
212          inject: true,
213          prefix: 'common/assets/meta-[hash]',
214          favicons: {
215            appDescription: 'Ethereum web interface',
216            display: 'standalone',
217            theme_color: '#007896'
218          }
219        }),
220        new SriPlugin({
221          hashFuncNames: ['sha256', 'sha384'],
222          enabled: true
223        }),
224        new ProgressPlugin(),
225        new ClearDistPlugin()
226      );
227    } else {
228      plugins.push(
229        // new AutoDllPlugin({
230        //   inject: true, // will inject the DLL bundles to index.html
231        //   filename: '[name]_[hash].js',
232        //   debug: true,
233        //   context: path.join(config.path.root),
234        //   entry: {
235        //     vendor: [...config.vendorModules, 'babel-polyfill', 'bootstrap-sass', 'font-awesome']
236        //   }
237        // }),
238        new HardSourceWebpackPlugin({
239          environmentHash: {
240            root: process.cwd(),
241            directories: ['common/webpack_config'],
242            files: ['package.json']
243          }
244        }),
245        new webpack.HotModuleReplacementPlugin(),
246        new FriendlyErrorsPlugin()
247      );
248    }
249  
250    if (options.isElectronBuild) {
251      // target: 'electron-renderer' kills scrypt, so manually pull in some
252      // of its configuration instead
253      plugins.push(
254        new webpack.ExternalsPlugin('commonjs', [
255          'desktop-capturer',
256          'electron',
257          'ipc',
258          'ipc-renderer',
259          'remote',
260          'web-frame',
261          'clipboard',
262          'crash-reporter',
263          'native-image',
264          'screen',
265          'shell'
266        ])
267      );
268    }
269  
270    // ====================
271    // === Optimization ===
272    // ====================
273    const optimization = {};
274    if (options.isProduction) {
275      optimization.splitChunks = {
276        chunks: 'all'
277      };
278      optimization.concatenateModules = false;
279    }
280  
281    // ====================
282    // ====== DevTool =====
283    // ====================
284    let devtool = false;
285    if (!options.isProduction) {
286      if (process.env.VSCODE_DEBUG) {
287        devtool = 'cheap-module-source-map';
288      } else {
289        devtool = 'cheap-module-eval-source-map';
290      }
291    }
292  
293    // ====================
294    // ====== Output ======
295    // ====================
296    const output = {
297      path: path.resolve(config.path.output, options.outputDir),
298      filename: options.isProduction ? `[name].${commitHash}.js` : '[name].js',
299      publicPath: isDownloadable && options.isProduction ? './' : '/',
300      crossOriginLoading: 'anonymous',
301      // Fix workers & HMR https://github.com/webpack/webpack/issues/6642
302      globalObject: options.isProduction ? undefined : 'self'
303    };
304  
305    // The final bundle
306    return {
307      devtool,
308      entry,
309      output,
310      module: { rules },
311      plugins,
312      target: 'web',
313      resolve: config.resolve,
314      performance: {
315        hints: options.isProduction ? 'warning' : false
316      },
317      optimization,
318      mode: options.isProduction ? 'production' : 'development',
319      stats: {
320        // Reduce build output
321        children: false,
322        chunks: false,
323        chunkModules: false,
324        chunkOrigins: false,
325        modules: false
326      }
327    };
328  };