/ 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 };