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