const path = require('path'); const webpack = require('webpack'); const PnpWebpackPlugin = require('pnp-webpack-plugin'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const pckg = require('./package.json'); const absolutePath = location => path.resolve(__dirname, location); const locations = { // Shouldn't change root: absolutePath('.'), output: absolutePath('web'), rootHtml: absolutePath('web/index.html'), packageJson: absolutePath('package.json'), appMain: absolutePath(pckg.main), // TODO: Bacon: Only use this in expo/apps/ modules: absolutePath('../../node_modules'), }; function getAppManifest() { const nativeAppManifest = require(absolutePath('./app.json')); if (nativeAppManifest && nativeAppManifest.expo) { const { expo } = nativeAppManifest; const PWAManifest = require(absolutePath('./web/manifest.json')); const web = PWAManifest || {}; return { // facebookScheme // facebookAppId // facebookDisplayName name: expo.name, description: expo.description, slug: expo.slug, sdkVersion: expo.sdkVersion, version: expo.version, githubUrl: expo.githubUrl, orientation: expo.orientation, primaryColor: expo.primaryColor, privacy: expo.privacy, icon: expo.icon, scheme: expo.scheme, notification: expo.notification, splash: expo.splash, androidShowExponentNotificationInShellApp: expo.androidShowExponentNotificationInShellApp, web, }; } return {}; } const environment = process.env.NODE_ENV || 'development'; const __DEV__ = environment !== 'production'; const REACT_APP = /^REACT_APP_/i; const publicUrl = ''; function getClientEnvironment(publicUrl) { let processEnv = Object.keys(process.env) .filter(key => REACT_APP.test(key)) .reduce( (env, key) => { env[key] = JSON.stringify(process.env[key]); return env; }, { // Useful for determining whether we’re running in production mode. // Most importantly, it switches React into the correct mode. NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'), // Useful for resolving the correct path to static assets in `public`. // For example, . // This should only be used as an escape hatch. Normally you would put // images into the root folder and `import` them in code to get their paths. PUBLIC_URL: JSON.stringify(publicUrl), // Surface the manifest for use in expo-constants APP_MANIFEST: JSON.stringify(getAppManifest()), } ); return { 'process.env': processEnv, __DEV__, }; } const env = getClientEnvironment(publicUrl); const includeModule = module => { return path.resolve(locations.modules, module); }; // Only compile files from react-native, and expo libraries. const includeModulesThatContainPaths = [ 'node_modules/react-native', 'node_modules/react-navigation', 'node_modules/expo', 'node_modules/@react', 'node_modules/@expo/', // Special case for this app 'apps/test-suite', ]; const babelLoaderConfiguration = { test: /\.jsx?$/, include(inputPath) { for (const option of includeModulesThatContainPaths) { if (inputPath.includes(option)) { return inputPath; } } return null; }, use: { loader: 'babel-loader', options: { babelrc: false, }, }, }; // This is needed for webpack to import static images in JavaScript files. const imageLoaderConfiguration = { test: /\.(gif|jpe?g|png|svg)$/, use: { loader: 'url-loader', options: { limit: 10000, name: '[name].[ext]', }, }, }; // This is needed for loading css const cssLoaderConfiguration = { test: /\.css$/, use: ['style-loader', 'css-loader'], }; const ttfLoaderConfiguration = { test: /\.ttf$/, use: [ { loader: 'file-loader', options: { name: './fonts/[hash].[ext]', }, }, ], include: [ locations.root, includeModule('react-native-vector-icons'), includeModule('@expo/vector-icons'), ], }; const htmlLoaderConfiguration = { test: /\.html$/, use: ['html-loader'], include: [absolutePath('./assets')], }; const mediaLoaderConfiguration = { test: /\.(mov|mp4|mp3|wav|webm|db)$/, use: [ { loader: 'file-loader', options: { name: '[path][name].[ext]', }, }, ], }; // This method intercepts modules being referenced in react-native // and redirects them to web friendly versions in expo. function getWebModule(initialRoot, moduleName) { return function(res) { if (res.context.includes('node_modules/react-native/')) { res.request = includeModule(initialRoot + moduleName); } }; } function useWebModule(modulePathToHiJack, redirectPath, initialRoot = 'expo/build/web/') { return new webpack.NormalModuleReplacementPlugin( new RegExp(modulePathToHiJack), getWebModule(initialRoot, redirectPath) ); } const publicPath = '/'; module.exports = { mode: 'development', devtool: 'cheap-module-source-map', entry: [require.resolve('react-dev-utils/webpackHotDevClient'), locations.appMain], // configures where the build ends up output: { path: locations.output, pathinfo: true, filename: 'bundle.js', // There are also additional JS chunk files if you use code splitting. chunkFilename: '[name].chunk.js', // This is the URL that app is served from. We use "/" in development. publicPath, // Point sourcemap entries to original disk location (format as URL on Windows) devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), }, optimization: { runtimeChunk: true, }, devServer: { progress: true, historyApiFallback: true, compress: true, disableHostCheck: true, contentBase: locations.output, inline: true, }, module: { rules: [ { parser: { requireEnsure: false } }, htmlLoaderConfiguration, babelLoaderConfiguration, cssLoaderConfiguration, imageLoaderConfiguration, ttfLoaderConfiguration, mediaLoaderConfiguration, ], }, plugins: [ // Generates an `index.html` file with the