1const path = require('path'); 2const webpack = require('webpack'); 3const PnpWebpackPlugin = require('pnp-webpack-plugin'); 4const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); 5const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); 6const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); 7const HtmlWebpackPlugin = require('html-webpack-plugin'); 8const ManifestPlugin = require('webpack-manifest-plugin'); 9const pckg = require('./package.json'); 10 11const absolutePath = location => path.resolve(__dirname, location); 12 13const locations = { 14 // Shouldn't change 15 root: absolutePath('.'), 16 output: absolutePath('web'), 17 rootHtml: absolutePath('web/index.html'), 18 packageJson: absolutePath('package.json'), 19 appMain: absolutePath(pckg.main), 20 21 // TODO: Bacon: Only use this in expo/apps/ 22 modules: absolutePath('../../node_modules'), 23}; 24 25const environment = process.env.NODE_ENV || 'development'; 26const __DEV__ = environment !== 'production'; 27 28const REACT_APP = /^REACT_APP_/i; 29 30const publicUrl = ''; 31 32function getClientEnvironment(publicUrl) { 33 let processEnv = Object.keys(process.env) 34 .filter(key => REACT_APP.test(key)) 35 .reduce( 36 (env, key) => { 37 env[key] = JSON.stringify(process.env[key]); 38 return env; 39 }, 40 { 41 // Useful for determining whether we’re running in production mode. 42 // Most importantly, it switches React into the correct mode. 43 NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'), 44 45 // Useful for resolving the correct path to static assets in `public`. 46 // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />. 47 // This should only be used as an escape hatch. Normally you would put 48 // images into the `src` and `import` them in code to get their paths. 49 PUBLIC_URL: JSON.stringify(publicUrl), 50 } 51 ); 52 return { 53 'process.env': processEnv, 54 __DEV__, 55 }; 56} 57 58const env = getClientEnvironment(publicUrl); 59 60const includeModule = module => { 61 return path.resolve(locations.modules, module); 62}; 63 64const babelLoaderConfiguration = { 65 test: /\.jsx?$/, 66 include: [ 67 // TODO: Bacon: This makes compilation take a while 68 locations.root, 69 locations.modules, 70 ], 71 use: { 72 loader: 'babel-loader', 73 options: { 74 babelrc: false, 75 }, 76 }, 77}; 78 79// This is needed for webpack to import static images in JavaScript files. 80const imageLoaderConfiguration = { 81 test: /\.(gif|jpe?g|png|svg)$/, 82 use: { 83 loader: 'url-loader', 84 options: { 85 limit: 10000, 86 name: '[name].[ext]', 87 }, 88 }, 89}; 90 91// This is needed for loading css 92const cssLoaderConfiguration = { 93 test: /\.css$/, 94 use: ['style-loader', 'css-loader'], 95}; 96 97const ttfLoaderConfiguration = { 98 test: /\.ttf$/, 99 use: [ 100 { 101 loader: 'file-loader', 102 options: { 103 name: './fonts/[hash].[ext]', 104 }, 105 }, 106 ], 107 include: [ 108 locations.root, 109 includeModule('react-native-vector-icons'), 110 includeModule('@expo/vector-icons'), 111 ], 112}; 113 114const htmlLoaderConfiguration = { 115 test: /\.html$/, 116 use: ['html-loader'], 117 include: [absolutePath('./assets')], 118}; 119 120const videoLoaderConfiguration = { 121 test: /\.(mov|mp4)$/, 122 use: [ 123 { 124 loader: 'file-loader', 125 options: { 126 name: '[path][name].[ext]', 127 }, 128 }, 129 ], 130}; 131 132function getWebModule(moduleName, initialRoot, logTag) { 133 return function(res) { 134 if (res.context.indexOf('node_modules/react-native/') === -1) return; 135 res.request = includeModule(initialRoot + moduleName); 136 }; 137} 138 139function useWebModules(modules, initialRoot = 'react-native-web/dist/exports/', logTag) { 140 return modules.map(module => { 141 let moduleName = module; 142 let proxyName = module; 143 if (Array.isArray(module)) { 144 moduleName = module[0]; 145 proxyName = module[1]; 146 } 147 return new webpack.NormalModuleReplacementPlugin( 148 new RegExp(moduleName), 149 getWebModule(proxyName, initialRoot, logTag) 150 ); 151 }); 152} 153 154const publicPath = '/'; 155 156module.exports = { 157 mode: 'development', 158 devtool: 'cheap-module-source-map', 159 160 entry: [require.resolve('react-dev-utils/webpackHotDevClient'), locations.appMain], 161 // configures where the build ends up 162 output: { 163 path: locations.output, 164 pathinfo: true, 165 filename: 'bundle.js', 166 // There are also additional JS chunk files if you use code splitting. 167 chunkFilename: '[name].chunk.js', 168 // This is the URL that app is served from. We use "/" in development. 169 publicPath, 170 // Point sourcemap entries to original disk location (format as URL on Windows) 171 devtoolModuleFilenameTemplate: info => 172 path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), 173 }, 174 optimization: { 175 runtimeChunk: true, 176 }, 177 devServer: { 178 progress: true, 179 historyApiFallback: true, 180 compress: true, 181 disableHostCheck: true, 182 contentBase: locations.output, 183 inline: true, 184 }, 185 module: { 186 rules: [ 187 { parser: { requireEnsure: false } }, 188 189 htmlLoaderConfiguration, 190 babelLoaderConfiguration, 191 cssLoaderConfiguration, 192 imageLoaderConfiguration, 193 ttfLoaderConfiguration, 194 videoLoaderConfiguration, 195 ], 196 }, 197 plugins: [ 198 // Generates an `index.html` file with the <script> injected. 199 new HtmlWebpackPlugin({ 200 inject: true, 201 template: locations.rootHtml, 202 filename: locations.rootHtml, 203 title: pckg.name, 204 }), 205 new InterpolateHtmlPlugin(HtmlWebpackPlugin, { 206 PUBLIC_URL: publicUrl, 207 }), 208 209 new webpack.HotModuleReplacementPlugin(), 210 211 new CaseSensitivePathsPlugin(), 212 213 // Moment.js is an extremely popular library that bundles large locale files 214 // by default due to how Webpack interprets its code. This is a practical 215 // solution that requires the user to opt into importing specific locales. 216 // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack 217 // You can remove this if you don't use Moment.js: 218 /* expo-localization uses `moment/timezone` */ 219 new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 220 // Generate a manifest file which contains a mapping of all asset filenames 221 // to their corresponding output file so that tools can pick it up without 222 // having to parse `index.html`. 223 new ManifestPlugin({ 224 fileName: 'asset-manifest.json', 225 publicPath, 226 }), 227 228 new webpack.DefinePlugin(env), 229 ...useWebModules(['Platform', 'DeviceInfo', 'Dimensions', 'Linking', 'Image', 'Share', 'Text']), 230 ...useWebModules( 231 [ 232 'Performance/Systrace', 233 ['HMRLoadingView', 'Utilities/HMRLoadingView'], 234 ['RCTNetworking', 'Network/RCTNetworking'], 235 ], 236 'expo/build/web/' 237 ), 238 ], 239 resolve: { 240 symlinks: false, 241 extensions: ['.web.js', '.js', '.jsx', '.json'], 242 alias: { 243 'react-native$': 'react-native-web', 244 }, 245 plugins: [ 246 // Adds support for installing with Plug'n'Play, leading to faster installs and adding 247 // guards against forgotten dependencies and such. 248 PnpWebpackPlugin, 249 // Prevents users from importing files from outside of src/ (or node_modules/). 250 // This often causes confusion because we only process files within src/ with babel. 251 // To fix this, we prevent you from importing files out of src/ -- if you'd like to, 252 // please link the files into your node_modules/ and let module-resolution kick in. 253 // Make sure your source files are compiled, as they will not be processed in any way. 254 new ModuleScopePlugin(locations.output, [locations.packageJson]), 255 ], 256 }, 257 resolveLoader: { 258 plugins: [ 259 // Also related to Plug'n'Play, but this time it tells Webpack to load its loaders 260 // from the current package. 261 PnpWebpackPlugin.moduleLoader(module), 262 ], 263 }, 264 // Some libraries import Node modules but don't use them in the browser. 265 // Tell Webpack to provide empty mocks for them so importing them works. 266 node: { 267 dgram: 'empty', 268 fs: 'empty', 269 net: 'empty', 270 tls: 'empty', 271 child_process: 'empty', 272 }, 273 // Turn off performance processing because we utilize 274 // our own hints via the FileSizeReporter 275 performance: false, 276}; 277