webpack.config.js (5495B)
1 'use strict'; 2 3 const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 4 const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 7 const StyleLintPlugin = require('stylelint-webpack-plugin'); 8 const TerserPlugin = require('terser-webpack-plugin'); 9 const Webpack = require('webpack'); 10 11 const paths = require('./paths'); 12 13 const errorExitStatus = 1; 14 15 const envs = { 16 production: true, 17 development: true, 18 testing: true, 19 }; 20 21 const mode = process.env.NODE_ENV; 22 23 if (!envs[mode]) { 24 console.log(`NODE_ENV has invalid value "${mode}"`); 25 process.exit(errorExitStatus); 26 } 27 28 const isProduction = mode === 'production'; 29 const isDevelopment = mode === 'development'; 30 const isTesting = mode === 'testing'; 31 32 const productionOutput = { 33 path: paths.appBuild, 34 filename: 'static/js/[name].[contenthash:8].js', 35 }; 36 37 const babelConfig = { 38 presets: [ 39 [ 40 '@babel/preset-env', 41 { 42 useBuiltIns: 'usage', 43 corejs: {version: '3.8', proposals: true}, 44 modules: 'commonjs', 45 exclude: ['proposal-dynamic-import'], 46 }, 47 ], 48 ], 49 overrides: [ 50 { 51 test: './src/vendored/github.com/augustl/js-unzip/js-unzip.js', 52 sourceType: 'script', 53 }, 54 ], 55 56 plugins: [ 57 '@babel/plugin-syntax-dynamic-import', 58 '@babel/plugin-proposal-class-properties', 59 '@babel/plugin-proposal-optional-chaining', 60 '@babel/plugin-proposal-nullish-coalescing-operator', 61 ...(isTesting ? ['babel-plugin-rewire'] : []), 62 ], 63 }; 64 65 const sourceMapOption = { 66 filename: '[file].map', 67 columns: isProduction, 68 exclude: /mapillary/u, 69 }; 70 71 const DevToolPlugin = isProduction ? Webpack.SourceMapDevToolPlugin : Webpack.EvalSourceMapDevToolPlugin; 72 73 const plugins = [ 74 ...(isProduction ? [new CleanWebpackPlugin()] : []), 75 ...(isProduction ? [new CopyWebpackPlugin({patterns: [{from: paths.appPublic, to: ''}]})] : []), 76 new HtmlWebpackPlugin({ 77 template: paths.appIndexHtml, 78 minify: false, 79 }), 80 ...(isProduction || isDevelopment 81 ? [ 82 new MiniCssExtractPlugin({ 83 filename: 'static/css/[name].[contenthash:8].css', 84 }), 85 ] 86 : []), 87 new Webpack.DefinePlugin({ 88 NODE_ENV: JSON.stringify(mode), 89 RELEASE_VER: JSON.stringify(process.env.RELEASE_VER || 'local devel'), 90 }), 91 ...(isProduction || isDevelopment 92 ? [ 93 new StyleLintPlugin({ 94 config: {extends: 'stylelint-config-recommended'}, 95 files: ['src/**/*.css', 'vendored/**/*.css'], 96 emitWarning: isDevelopment, 97 emitError: isProduction, 98 }), 99 ] 100 : []), 101 new DevToolPlugin(sourceMapOption), 102 ]; 103 104 const productionCSSLoader = [ 105 MiniCssExtractPlugin.loader, 106 {loader: 'css-loader', options: {importLoaders: 1}}, 107 { 108 loader: 'postcss-loader', 109 options: { 110 ident: 'postcss', 111 plugins: () => [require('postcss-import')(), require('postcss-preset-env')(), require('cssnano')()], 112 }, 113 }, 114 ]; 115 116 const developmentCSSLoader = ['style-loader', {loader: 'css-loader', options: {importLoaders: 1}}]; 117 118 const loaders = [ 119 { 120 test: /\.mjs$/u, 121 include: /node_modules/u, 122 type: 'javascript/auto', 123 }, 124 { 125 test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/u, 126 type: 'asset/inline', 127 }, 128 { 129 test: /\.(html)(\?.*)?$/u, 130 loader: 'raw-loader', 131 }, 132 { 133 test: /\.csv$/u, 134 loader: 'csv-loader', 135 options: { 136 dynamicTyping: true, 137 header: true, 138 skipEmptyLines: true, 139 }, 140 }, 141 142 ...(isProduction || isDevelopment 143 ? [ 144 { 145 test: /\.js$/u, 146 include: paths.appSrc, 147 enforce: 'pre', 148 loader: 'eslint-loader', 149 options: { 150 emitWarning: !isProduction, 151 }, 152 }, 153 ] 154 : []), 155 156 { 157 test: /\.js$/u, 158 exclude: isProduction ? [/node_modules\/core-js/u, /node_modules\/webpack/u] : /node_modules/u, 159 use: [ 160 { 161 loader: 'babel-loader', 162 options: babelConfig, 163 }, 164 ], 165 type: 'javascript/auto', 166 }, 167 168 { 169 test: /\.s?css/iu, 170 use: isProduction ? productionCSSLoader : developmentCSSLoader, 171 }, 172 ]; 173 174 module.exports = { 175 mode: isProduction ? 'production' : 'development', 176 devtool: false, 177 stats: 'errors-warnings', 178 bail: isProduction || isTesting, 179 devServer: { 180 client: { 181 overlay: false, 182 }, 183 }, 184 185 entry: { 186 app: paths.appIndexJs, 187 }, 188 189 optimization: { 190 splitChunks: { 191 chunks: 'all', 192 }, 193 runtimeChunk: 'single', 194 minimizer: [ 195 new TerserPlugin({ 196 parallel: true, 197 exclude: /node_modules\/mapillary/u, 198 }), 199 ], 200 }, 201 202 resolve: { 203 alias: { 204 '~': paths.appSrc, 205 }, 206 }, 207 208 output: isProduction ? productionOutput : {}, 209 plugins: plugins, 210 module: { 211 rules: loaders, 212 }, 213 };