diff --git a/packages/react-error-overlay/build.js b/packages/react-error-overlay/build.js
new file mode 100644
index 0000000000000000000000000000000000000000..592da141ffeefc9a13e327c2c9871be7df398555
--- /dev/null
+++ b/packages/react-error-overlay/build.js
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+const webpack = require('webpack');
+const chalk = require('chalk');
+const webpackConfig = require('./webpack.config.js');
+const iframeWebpackConfig = require('./webpack.config.iframe.js');
+const rimraf = require('rimraf');
+const chokidar = require('chokidar');
+
+const args = process.argv.slice(2);
+const watchMode = args[0] === '--watch' || args[0] === '-w';
+
+const isCI =
+  process.env.CI &&
+  (typeof process.env.CI !== 'string' ||
+    process.env.CI.toLowerCase() !== 'false');
+
+function build(config, name, callback) {
+  console.log(chalk.cyan('Compiling ' + name));
+  webpack(config).run((error, stats) => {
+    if (error) {
+      console.log(chalk.red('Failed to compile.'));
+      console.log(error.message || error);
+      console.log();
+    }
+
+    if (stats.compilation.errors.length) {
+      console.log(chalk.red('Failed to compile.'));
+      console.log(stats.toString({ all: false, errors: true }));
+    }
+
+    if (stats.compilation.warnings.length) {
+      console.log(chalk.yellow('Compiled with warnings.'));
+      console.log(stats.toString({ all: false, warnings: true }));
+    }
+
+    // Fail the build if running in a CI server
+    if (
+      error ||
+      stats.compilation.errors.length ||
+      stats.compilation.warnings.length
+    ) {
+      isCI && process.exit(1);
+      return;
+    }
+
+    console.log(
+      stats.toString({ colors: true, modules: false, version: false })
+    );
+    console.log();
+
+    callback(stats);
+  });
+}
+
+function runBuildSteps() {
+  build(iframeWebpackConfig, 'iframeScript.js', () => {
+    build(webpackConfig, 'index.js', () => {
+      console.log(chalk.bold.green('Compiled successfully!\n\n'));
+    });
+  });
+}
+
+function setupWatch() {
+  const watcher = chokidar.watch('./src', {
+    ignoreInitial: true,
+  });
+
+  watcher.on('change', runBuildSteps);
+  watcher.on('add', runBuildSteps);
+
+  watcher.on('ready', () => {
+    runBuildSteps();
+  });
+
+  process.on('SIGINT', function() {
+    watcher.close();
+    process.exit(0);
+  });
+
+  watcher.on('error', error => {
+    console.error('Watcher failure', error);
+    process.exit(1);
+  });
+}
+
+// Clean up lib folder
+rimraf('lib/', () => {
+  console.log('Cleaned up the lib folder.\n');
+  watchMode ? setupWatch() : runBuildSteps();
+});
diff --git a/packages/react-error-overlay/package.json b/packages/react-error-overlay/package.json
index 7640d1c5d9264a91b9e6134bec19bf054d86b2f4..82ae43ec62819529a91adca2a2bdd6d64cd83154 100644
--- a/packages/react-error-overlay/package.json
+++ b/packages/react-error-overlay/package.json
@@ -5,10 +5,10 @@
   "main": "lib/index.js",
   "scripts": {
     "prepublishOnly": "npm run build:prod && npm test",
-    "start": "rimraf lib/ && cross-env NODE_ENV=development npm run build -- --watch",
-    "test": "flow && jest",
-    "build": "rimraf lib/ && babel src/ -d lib/",
-    "build:prod": "rimraf lib/ && cross-env NODE_ENV=production babel src/ -d lib/"
+    "start": "cross-env NODE_ENV=development node build.js --watch",
+    "test": "flow && cross-env NODE_ENV=test jest",
+    "build": "cross-env NODE_ENV=development node build.js",
+    "build:prod": "cross-env NODE_ENV=production node build.js"
   },
   "repository": "facebookincubator/create-react-app",
   "license": "MIT",
@@ -35,15 +35,19 @@
     "babel-code-frame": "6.22.0",
     "babel-runtime": "6.26.0",
     "html-entities": "1.2.1",
+    "object-assign": "4.1.1",
+    "promise": "8.0.1",
     "react": "^15 || ^16",
     "react-dom": "^15 || ^16",
     "settle-promise": "1.0.0",
     "source-map": "0.5.6"
   },
   "devDependencies": {
-    "babel-cli": "6.24.1",
     "babel-eslint": "7.2.3",
     "babel-preset-react-app": "^3.0.3",
+    "babel-loader": "^7.1.2",
+    "chalk": "^2.1.0",
+    "chokidar": "^1.7.0",
     "cross-env": "5.0.5",
     "eslint": "4.4.1",
     "eslint-config-react-app": "^2.0.1",
@@ -54,7 +58,9 @@
     "flow-bin": "^0.54.0",
     "jest": "20.0.4",
     "jest-fetch-mock": "1.2.1",
-    "rimraf": "^2.6.1"
+    "raw-loader": "^0.5.1",
+    "rimraf": "^2.6.1",
+    "webpack": "^3.6.0"
   },
   "jest": {
     "setupFiles": [
diff --git a/packages/react-error-overlay/src/iframeScript.js b/packages/react-error-overlay/src/iframeScript.js
new file mode 100644
index 0000000000000000000000000000000000000000..c95ea36b1a4e2c38ec9efa300631264b35a4c838
--- /dev/null
+++ b/packages/react-error-overlay/src/iframeScript.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import './utils/pollyfills.js';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import CompileErrorContainer from './containers/CompileErrorContainer';
+import RuntimeErrorContainer from './containers/RuntimeErrorContainer';
+import { overlayStyle } from './styles';
+import { applyStyles } from './utils/dom/css';
+
+let iframeRoot = null;
+
+function render({
+  currentBuildError,
+  currentRuntimeErrorRecords,
+  dismissRuntimeErrors,
+  launchEditorEndpoint,
+}) {
+  if (currentBuildError) {
+    return <CompileErrorContainer error={currentBuildError} />;
+  }
+  if (currentRuntimeErrorRecords.length > 0) {
+    return (
+      <RuntimeErrorContainer
+        errorRecords={currentRuntimeErrorRecords}
+        close={dismissRuntimeErrors}
+        launchEditorEndpoint={launchEditorEndpoint}
+      />
+    );
+  }
+  return null;
+}
+
+window.updateContent = function updateContent(errorOverlayProps) {
+  let renderedElement = render(errorOverlayProps);
+
+  if (renderedElement === null) {
+    ReactDOM.unmountComponentAtNode(iframeRoot);
+    return false;
+  }
+  // Update the overlay
+  ReactDOM.render(renderedElement, iframeRoot);
+  return true;
+};
+
+document.body.style.margin = '0';
+// Keep popup within body boundaries for iOS Safari
+document.body.style['max-width'] = '100vw';
+iframeRoot = document.createElement('div');
+applyStyles(iframeRoot, overlayStyle);
+document.body.appendChild(iframeRoot);
+window.parent.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__.iframeReady();
diff --git a/packages/react-error-overlay/src/index.js b/packages/react-error-overlay/src/index.js
index 23f9e783885763141659e4634fed0a4b3714c77e..52ff9199bcba6b131b1db7a2c74e8539f927eba1 100644
--- a/packages/react-error-overlay/src/index.js
+++ b/packages/react-error-overlay/src/index.js
@@ -6,15 +6,15 @@
  */
 
 /* @flow */
-import React from 'react';
-import type { Element } from 'react';
-import ReactDOM from 'react-dom';
-import CompileErrorContainer from './containers/CompileErrorContainer';
-import RuntimeErrorContainer from './containers/RuntimeErrorContainer';
 import { listenToRuntimeErrors } from './listenToRuntimeErrors';
-import { iframeStyle, overlayStyle } from './styles';
+import { iframeStyle } from './styles';
 import { applyStyles } from './utils/dom/css';
 
+// Importing iframe-bundle generated in the pre build step as
+// a text using webpack raw-loader. See webpack.config.js file.
+// $FlowFixMe
+import iframeScript from 'iframeScript';
+
 import type { ErrorRecord } from './listenToRuntimeErrors';
 
 type RuntimeReportingOptions = {|
@@ -25,8 +25,8 @@ type RuntimeReportingOptions = {|
 
 let iframe: null | HTMLIFrameElement = null;
 let isLoadingIframe: boolean = false;
+var isIframeReady: boolean = false;
 
-let renderedElement: null | Element<any> = null;
 let currentBuildError: null | string = null;
 let currentRuntimeErrorRecords: Array<ErrorRecord> = [];
 let currentRuntimeErrorOptions: null | RuntimeReportingOptions = null;
@@ -88,15 +88,14 @@ export function stopReportingRuntimeErrors() {
 }
 
 function update() {
-  renderedElement = render();
   // Loading iframe can be either sync or async depending on the browser.
   if (isLoadingIframe) {
     // Iframe is loading.
     // First render will happen soon--don't need to do anything.
     return;
   }
-  if (iframe) {
-    // Iframe has already loaded.
+  if (isIframeReady) {
+    // Iframe is ready.
     // Just update it.
     updateIframeContent();
     return;
@@ -108,58 +107,46 @@ function update() {
   loadingIframe.onload = function() {
     const iframeDocument = loadingIframe.contentDocument;
     if (iframeDocument != null && iframeDocument.body != null) {
-      iframeDocument.body.style.margin = '0';
-      // Keep popup within body boundaries for iOS Safari
-      iframeDocument.body.style['max-width'] = '100vw';
-      const iframeRoot = iframeDocument.createElement('div');
-      applyStyles(iframeRoot, overlayStyle);
-      iframeDocument.body.appendChild(iframeRoot);
-
-      // Ready! Now we can update the UI.
       iframe = loadingIframe;
-      isLoadingIframe = false;
-      updateIframeContent();
+      const script = loadingIframe.contentWindow.document.createElement(
+        'script'
+      );
+      script.type = 'text/javascript';
+      script.innerHTML = iframeScript;
+      iframeDocument.body.appendChild(script);
     }
   };
   const appDocument = window.document;
   appDocument.body.appendChild(loadingIframe);
 }
 
-function render() {
-  if (currentBuildError) {
-    return <CompileErrorContainer error={currentBuildError} />;
-  }
-  if (currentRuntimeErrorRecords.length > 0) {
-    if (!currentRuntimeErrorOptions) {
-      throw new Error('Expected options to be injected.');
-    }
-    return (
-      <RuntimeErrorContainer
-        errorRecords={currentRuntimeErrorRecords}
-        close={dismissRuntimeErrors}
-        launchEditorEndpoint={currentRuntimeErrorOptions.launchEditorEndpoint}
-      />
-    );
+function updateIframeContent() {
+  if (!currentRuntimeErrorOptions) {
+    throw new Error('Expected options to be injected.');
   }
-  return null;
-}
 
-function updateIframeContent() {
-  if (iframe === null) {
+  if (!iframe) {
     throw new Error('Iframe has not been created yet.');
   }
-  const iframeBody = iframe.contentDocument.body;
-  if (!iframeBody) {
-    throw new Error('Expected iframe to have a body.');
-  }
-  const iframeRoot = iframeBody.firstChild;
-  if (renderedElement === null) {
-    // Destroy iframe and force it to be recreated on next error
+
+  const isRendered = iframe.contentWindow.updateContent({
+    currentBuildError,
+    currentRuntimeErrorRecords,
+    dismissRuntimeErrors,
+    launchEditorEndpoint: currentRuntimeErrorOptions.launchEditorEndpoint,
+  });
+
+  if (!isRendered) {
     window.document.body.removeChild(iframe);
-    ReactDOM.unmountComponentAtNode(iframeRoot);
     iframe = null;
-    return;
+    isIframeReady = false;
   }
-  // Update the overlay
-  ReactDOM.render(renderedElement, iframeRoot);
 }
+
+window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ =
+  window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ || {};
+window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__.iframeReady = function iframeReady() {
+  isIframeReady = true;
+  isLoadingIframe = false;
+  updateIframeContent();
+};
diff --git a/packages/react-error-overlay/src/utils/pollyfills.js b/packages/react-error-overlay/src/utils/pollyfills.js
new file mode 100644
index 0000000000000000000000000000000000000000..ddd5aeb9651544568390c7e740c4a564053dd9a3
--- /dev/null
+++ b/packages/react-error-overlay/src/utils/pollyfills.js
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+if (typeof Promise === 'undefined') {
+  // Rejection tracking prevents a common issue where React gets into an
+  // inconsistent state due to an error, but it gets swallowed by a Promise,
+  // and the user has no idea what causes React's erratic future behavior.
+  require('promise/lib/rejection-tracking').enable();
+  window.Promise = require('promise/lib/es6-extensions.js');
+}
+
+// Object.assign() is commonly used with React.
+// It will use the native implementation if it's present and isn't buggy.
+Object.assign = require('object-assign');
diff --git a/packages/react-error-overlay/webpack.config.iframe.js b/packages/react-error-overlay/webpack.config.iframe.js
new file mode 100644
index 0000000000000000000000000000000000000000..9fa742b720bf6b17baaeb36538d8a1d08b160342
--- /dev/null
+++ b/packages/react-error-overlay/webpack.config.iframe.js
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+'use strict';
+
+const path = require('path');
+
+module.exports = {
+  devtool: 'cheap-module-source-map',
+  entry: './src/iframeScript.js',
+  output: {
+    path: path.join(__dirname, './lib'),
+    filename: 'iframe-bundle.js',
+  },
+  module: {
+    rules: [
+      {
+        test: /\.js$/,
+        include: path.resolve(__dirname, './src'),
+        use: 'babel-loader',
+      },
+    ],
+  },
+};
diff --git a/packages/react-error-overlay/webpack.config.js b/packages/react-error-overlay/webpack.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..5d640e05ca3d13d7649f77f8e44e5b050c2bf871
--- /dev/null
+++ b/packages/react-error-overlay/webpack.config.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+'use strict';
+
+const path = require('path');
+
+module.exports = {
+  devtool: 'cheap-module-source-map',
+  entry: './src/index.js',
+  output: {
+    path: path.join(__dirname, './lib'),
+    filename: 'index.js',
+    library: 'ReactErrorOverlay',
+    libraryTarget: 'umd',
+  },
+  module: {
+    rules: [
+      {
+        test: /iframe-bundle\.js$/,
+        use: 'raw-loader',
+      },
+      {
+        test: /\.js$/,
+        include: path.resolve(__dirname, './src'),
+        use: 'babel-loader',
+      },
+    ],
+  },
+  resolve: {
+    alias: {
+      iframeScript$: path.resolve(__dirname, './lib/iframe-bundle.js'),
+    },
+  },
+};