-
-
Notifications
You must be signed in to change notification settings - Fork 27.2k
Make error overlay to run in the context of the iframe #3142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
92a79f4
0a2acf5
0b48c89
f569f45
a52592f
483e631
fd248f7
7796f54
272c491
4dff32a
57274a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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": "rimraf lib/ && cross-env NODE_ENV=development webpack --config webpack.config.js -w & NODE_ENV=development babel src/ -d lib/ -w", | ||
| "test": "flow && cross-env NODE_ENV=test jest", | ||
| "build": "rimraf lib/ && cross-env NODE_ENV=development babel src/ -d lib/ && cross-env NODE_ENV=production webpack --config webpack.config.js", | ||
| "build:prod": "rimraf lib/ && cross-env NODE_ENV=production babel src/ -d lib/ && cross-env NODE_ENV=production webpack --config webpack.config.js" | ||
| }, | ||
| "repository": "facebookincubator/create-react-app", | ||
| "license": "MIT", | ||
|
|
@@ -33,12 +33,14 @@ | |
| "dependencies": { | ||
| "anser": "1.4.1", | ||
| "babel-code-frame": "6.22.0", | ||
| "babel-loader": "^7.1.2", | ||
| "babel-runtime": "6.26.0", | ||
| "html-entities": "1.2.1", | ||
| "react": "^15 || ^16", | ||
| "react-dom": "^15 || ^16", | ||
| "settle-promise": "1.0.0", | ||
| "source-map": "0.5.6" | ||
| "source-map": "0.5.6", | ||
| "webpack": "^3.6.0" | ||
|
||
| }, | ||
| "devDependencies": { | ||
| "babel-cli": "6.24.1", | ||
|
|
@@ -54,6 +56,7 @@ | |
| "flow-bin": "^0.54.0", | ||
| "jest": "20.0.4", | ||
| "jest-fetch-mock": "1.2.1", | ||
| "raw-loader": "^0.5.1", | ||
|
||
| "rimraf": "^2.6.1" | ||
| }, | ||
| "jest": { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /** | ||
| * Copyright (c) 2015-present, Facebook, Inc. | ||
| * All rights reserved. | ||
| * | ||
| * This source code is licensed under the BSD-style license found in the | ||
| * LICENSE file in the root directory of this source tree. An additional grant | ||
| * of patent rights can be found in the PATENTS file in the same directory. | ||
| */ | ||
| 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(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'; | ||
|
|
||
| /* eslint-disable import/no-webpack-loader-syntax */ | ||
| //$FlowFixMe | ||
| import iframeScript from 'raw-loader!./bundle.js'; | ||
|
||
| /* eslint-enable import/no-webpack-loader-syntax */ | ||
|
|
||
| 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(); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| var path = require('path'); | ||
| var webpack = require('webpack'); | ||
|
|
||
| module.exports = { | ||
| devtool: 'cheap-module-source-map', | ||
| entry: './src/iframeScript.js', | ||
|
||
| output: { | ||
| path: path.join(__dirname, './lib'), | ||
| filename: 'bundle.js', | ||
| }, | ||
| module: { | ||
| rules: [ | ||
| { | ||
| test: /\.js$/, | ||
| include: path.resolve(__dirname, './src'), | ||
| use: [ | ||
| { | ||
| loader: 'babel-loader', | ||
| options: { | ||
| cacheDirectory: true, | ||
|
||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we still compiling with Babel? We should just let webpack handle all of this if we're making the switch to a bundle imo.
Not sure if we want to support legacy use of the package via
lib/, /cc @gaearon.edit: ah, I see -- we still import index which relies on some modules.
Wouldn't it be better to bundle this entire thing up -- the way it works currently is pretty confusing, unless we make the distinction and actually split the bundled code & unbundled into two separate directories; what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #3142 (comment)