From 35d8459b068f8c6e1b119356fbb244662e062ac5 Mon Sep 17 00:00:00 2001
From: Chris Beer <cabeer@stanford.edu>
Date: Fri, 29 May 2020 15:43:35 -0700
Subject: [PATCH] Use webpack-dev-server and HMR for development

---
 README.md                |   2 +-
 babel.config.js          |   4 +-
 package.json             |   6 ++-
 src/components/App.js    |   3 ++
 src/lib/MiradorViewer.js |   4 +-
 webpack.config.js        | 114 +++++++++++++++++++++------------------
 6 files changed, 75 insertions(+), 58 deletions(-)

diff --git a/README.md b/README.md
index 1b14c76d0..9bb055e2e 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Mirador local development requires [nodejs](https://nodejs.org/en/download/) to
 $ npm start
 ```
 
-Then navigate to [http://127.0.0.1:4444/\_\_tests\_\_/integration/mirador/](http://127.0.0.1:4444/\_\_tests\_\_/integration/mirador/)
+Then navigate to [http://127.0.0.1:4444/](http://127.0.0.1:4444/)
 
 ### Instantiating Mirador
 
diff --git a/babel.config.js b/babel.config.js
index bcd1b025b..f915591a4 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -84,7 +84,9 @@ module.exports = function (api) {
         'lodash',
       ],
     },
-    ]];
+    ],
+    'react-hot-loader/babel',
+  ];
 
   return {
     plugins,
diff --git a/package.json b/package.json
index 72cfb4d17..a7b341bee 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
     "build:watch": "webpack --watch --mode=development",
     "prepublishOnly": "npm run clean && npm run build:es && npm run build:cjs && npm run build",
     "size": "bundlewatch --config bundlewatch.config.json",
-    "start": "npm run build:dev && concurrently -k \"npm:build:watch\" \"npm:server -- -p 4444\""
+    "start": "webpack-dev-server --open"
   },
   "license": "Apache-2.0",
   "contributors": [
@@ -56,6 +56,7 @@
     "react-beautiful-dnd": "^11.0.4",
     "react-copy-to-clipboard": "^5.0.1",
     "react-full-screen": "^0.2.4",
+    "react-hot-loader": "^4.12.21",
     "react-i18next": "^10.11.4",
     "react-image": "^2.1.3",
     "react-mosaic-component": "^4.0.1",
@@ -120,7 +121,8 @@
     "unfetch": "^4.1.0",
     "url-polyfill": "^1.1.7",
     "webpack": "^4.35.3",
-    "webpack-cli": "^3.3.5"
+    "webpack-cli": "^3.3.5",
+    "webpack-dev-server": "^3.11.0"
   },
   "peerDependencies": {
     "react": "^16.8.3",
diff --git a/src/components/App.js b/src/components/App.js
index fc7814786..0ff8ff2da 100644
--- a/src/components/App.js
+++ b/src/components/App.js
@@ -1,4 +1,5 @@
 import React, { Component, lazy, Suspense } from 'react';
+import { hot } from 'react-hot-loader/root';
 import PropTypes from 'prop-types';
 import PluginProvider from '../extend/PluginProvider';
 import createRootReducer from '../state/reducers/rootReducer';
@@ -43,3 +44,5 @@ App.propTypes = {
 App.defaultProps = {
   plugins: [],
 };
+
+export default hot(App);
diff --git a/src/lib/MiradorViewer.js b/src/lib/MiradorViewer.js
index 43a65f7ba..5bad31bcf 100644
--- a/src/lib/MiradorViewer.js
+++ b/src/lib/MiradorViewer.js
@@ -2,7 +2,7 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import { Provider } from 'react-redux';
 import { v4 as uuid } from 'uuid';
-import { App } from '../components/App';
+import HotApp from '../components/App';
 import createStore from '../state/createStore';
 import * as actions from '../state/actions';
 import { getCompanionWindowIdsForPosition, getManifestSearchService } from '../state/selectors';
@@ -26,7 +26,7 @@ class MiradorViewer {
 
     ReactDOM.render(
       <Provider store={this.store}>
-        <App plugins={this.plugins} />
+        <HotApp plugins={this.plugins} />
       </Provider>,
       document.getElementById(config.id),
     );
diff --git a/webpack.config.js b/webpack.config.js
index d96e13da3..1b29192c6 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,63 +3,73 @@ const webpack = require('webpack');
 const TerserPlugin = require('terser-webpack-plugin');
 const paths = require('./config/paths');
 
-const babelLoaderConfig = {
-  include: paths.appPath, // CRL
-  loader: require.resolve('babel-loader'),
-  options: {
-    // Save disk space when time isn't as important
-    cacheCompression: true,
-    cacheDirectory: true,
-    compact: true,
-  },
-  test: /\.(js|mjs|jsx)$/,
-};
-const baseConfig = [
-  {
-    entry: ['./src/polyfills.js', './src/index.js'],
-    module: {
-      rules: [
-        babelLoaderConfig,
-        {
-          test: /\.css$/i,
-          use: ['style-loader', 'css-loader'],
+const baseConfig = {
+  entry: ['react-hot-loader/patch', './src/polyfills.js', './src/index.js'],
+  module: {
+    rules: [
+      {
+        include: paths.appPath, // CRL
+        loader: require.resolve('babel-loader'),
+        options: {
+          // Save disk space when time isn't as important
+          cacheCompression: true,
+          cacheDirectory: true,
+          compact: true,
         },
-      ],
-    },
-    optimization: {
-      minimizer: [
-        new TerserPlugin({
-          extractComments: true,
-        }),
-      ],
-    },
-    output: {
-      filename: 'mirador.min.js',
-      library: 'Mirador',
-      libraryExport: 'default',
-      libraryTarget: 'umd',
-      path: path.join(__dirname, 'dist'),
-      publicPath: '/dist/',
-    },
-    plugins: [
-      new webpack.IgnorePlugin({
-        resourceRegExp: /@blueprintjs\/(core|icons)/, // ignore optional UI framework dependencies
+        test: /\.(js|mjs|jsx)$/,
+      },
+      {
+        test: /\.css$/i,
+        use: ['style-loader', 'css-loader'],
+      },
+    ],
+  },
+  optimization: {
+    minimizer: [
+      new TerserPlugin({
+        extractComments: true,
       }),
     ],
-    resolve: { extensions: ['.js'] },
   },
-];
+  output: {
+    filename: 'mirador.min.js',
+    library: 'Mirador',
+    libraryExport: 'default',
+    libraryTarget: 'umd',
+    path: path.join(__dirname, 'dist'),
+    publicPath: '/dist/',
+  },
+  plugins: [
+    new webpack.IgnorePlugin({
+      resourceRegExp: /@blueprintjs\/(core|icons)/, // ignore optional UI framework dependencies
+    }),
+  ],
+  resolve: { extensions: ['.js'] },
+};
 
 module.exports = (env, options) => {
   const isProduction = options.mode === 'production';
-  return baseConfig.map((config) => {
-    if (isProduction) {
-      config.plugins.push(new webpack.optimize.LimitChunkCountPlugin({
-        maxChunks: 1,
-      }));
-    } else {
-      config.devtool = 'eval-source-map'; // eslint-disable-line no-param-reassign
-    }
-    return config;
-  });
+
+  if (isProduction) {
+    return {
+      ...baseConfig,
+      plugins: [
+        ...(baseConfig.plugins || []),
+        new webpack.optimize.LimitChunkCountPlugin({
+          maxChunks: 1,
+        }),
+      ],
+    };
+  }
+
+  return {
+    ...baseConfig,
+    devServer: {
+      contentBase: './__tests__/integration/mirador',
+      hot: true,
+      port: 4444,
+    },
+    devtool: 'eval-source-map',
+    mode: 'development',
+  };
 };
-- 
GitLab