Commit cd3d04b7 authored by Tharaka Wijebandara's avatar Tharaka Wijebandara Committed by GitHub
Browse files

Make error overlay to run in the context of the iframe (#3142)

* Make error overlay to run in the context of the iframe

* Configure webpack to build the entire package

* Remove inline raw-loader config

* Configure watch mode for error-overlay webpack build

* Add polyfills to the error-overlay iframe script

* Add header comment

* Configure to fail CI on error or warning

* Suppress flow-type error on importing iframe-bundle

* Change webpack to a dev dependency and pin some versions

* Disable webpack cache

* Update license headers to MIT
parent 01a0d737
3 merge requests!12191Lim.Pisey.168:/Identified - We are currently investigating reports of missing build logs. The issue has been identified and a resolution is in progress. We will provide a further update when available.Mar 21, 09:02 UTC,!12853brikk,!5717Automatically extract project file structure from build bundle file
Showing with 284 additions and 56 deletions
+284 -56
/**
* 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();
});
......@@ -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": [
......
/**
* 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();
......@@ -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();
};
/**
* 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');
/**
* 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',
},
],
},
};
/**
* 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'),
},
},
};
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment