Skip to content
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

Hydration DOM Fixture #13521

Merged
merged 37 commits into from
Sep 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
de6ea59
Manually join extra attributes in warning
nhunzaker Aug 13, 2018
5c67343
Add home component. Async load fixtures.
nhunzaker Aug 11, 2018
1546840
Tweak select width
nhunzaker Aug 11, 2018
a8d8ce8
Adds experimental hydration fixture
nhunzaker Aug 11, 2018
91cd8c2
Transform with buble
nhunzaker Aug 13, 2018
ce9a2b2
Eliminate dependencies
nhunzaker Aug 13, 2018
546cb07
Pull in react-live for better editing
nhunzaker Aug 13, 2018
9dad4a7
Handle encoding errors, pass react version
nhunzaker Aug 30, 2018
0c88335
Load the correct version of React
nhunzaker Aug 30, 2018
50856cc
Tweaks
nhunzaker Aug 30, 2018
05f3042
Revert style change
nhunzaker Aug 30, 2018
ad5c392
Revert warning update
nhunzaker Aug 30, 2018
7284c87
Properly handle script errors. Fix dom-server CDN loading
nhunzaker Aug 30, 2018
046a27f
Fix 15.x releases
nhunzaker Aug 30, 2018
eac9900
Use postMessage to reduce latency, support older browsers
nhunzaker Sep 6, 2018
2e69dc5
Fix fixture renamespacing bug
nhunzaker Sep 6, 2018
be904d1
Gracefully fallback to textarea in React 14
nhunzaker Sep 6, 2018
88b1dd8
Replace buble with babel, react-live with codemirror
nhunzaker Sep 6, 2018
84ac939
Simplify layout to resolve production code-mirror issues
nhunzaker Sep 6, 2018
b4ed966
Tweak height rules for code-mirror
nhunzaker Sep 6, 2018
8a40c04
Update theme to paraiso
nhunzaker Sep 6, 2018
17a957d
Format Code.js
nhunzaker Sep 6, 2018
9d2b4a9
Adjust viewport to fix CodeMirror resize issue in production build
nhunzaker Sep 6, 2018
6cd0e2e
Eliminate react-code-mirror
nhunzaker Sep 6, 2018
ecb1ea8
Improve error state. Make full stack collapsable
nhunzaker Sep 9, 2018
4b50198
Add link to license in codemirror stylesheet
nhunzaker Sep 9, 2018
3bf211e
Make code example more concise
nhunzaker Sep 9, 2018
437c45b
Replace "Hydrate" with "Auto-hydrate" for clarity
nhunzaker Sep 9, 2018
aa321e2
Remove border below hydration header
nhunzaker Sep 9, 2018
565ac72
Rename query function in render.js
nhunzaker Sep 9, 2018
d5a4acd
Use Function(code) to evaluate hydration fixture
nhunzaker Sep 9, 2018
ec2e1a2
Extend hydration fixture to fill width. Design adjustments
nhunzaker Sep 9, 2018
c7d4738
Improve error scroll state
nhunzaker Sep 9, 2018
e79cd06
Lazy load CodeMirror together before executing
nhunzaker Sep 9, 2018
bccb786
Fix indentation on error message
nhunzaker Sep 9, 2018
c1843bf
Do not highlight errors from Babel. Add setPrototypeOf polyfill
nhunzaker Sep 10, 2018
81dcf6c
Increase resilience to bad errors in Hydration fixture
nhunzaker Sep 10, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fixtures/dom/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ coverage
build
public/react.development.js
public/react-dom.development.js
public/react-dom-server.browser.development.js

# misc
.DS_Store
Expand Down
4 changes: 3 additions & 1 deletion fixtures/dom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"react-scripts": "^1.0.11"
},
"dependencies": {
"@babel/standalone": "^7.0.0",
"classnames": "^2.2.5",
"codemirror": "^5.40.0",
"core-js": "^2.4.1",
"prop-types": "^15.6.0",
"query-string": "^4.2.3",
Expand All @@ -16,7 +18,7 @@
},
"scripts": {
"start": "react-scripts start",
"prestart": "cp ../../build/dist/react.development.js public/ && cp ../../build/dist/react-dom.development.js public/",
"prestart": "cp ../../build/dist/react.development.js ../../build/dist/react-dom.development.js ../../build/dist/react-dom-server.browser.development.js public/",
"build": "react-scripts build && cp build/index.html build/200.html",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
Expand Down
86 changes: 86 additions & 0 deletions fixtures/dom/public/renderer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Renderer</title>
<style>
*,
*:before,
*:after {
box-sizing: border-box;
}

html,
body {
font-family: sans-serif;
margin: 0;
height: 100%;
}

body {
padding-top: 32px;
}

#status {
font-size: 12px;
left: 8px;
letter-spacing: 0.05em;
line-height: 16px;
margin: -8px 0 0;
max-width: 50%;
overflow: hidden;
position: absolute;
text-align: left;
text-overflow: ellipsis;
top: 50%;
white-space: nowrap;
width: 100%;
}

#output {
margin: 16px;
}

.header {
background: white;
border-bottom: 1px solid #d9d9d9;
padding: 4px;
top: 0;
left: 0;
position: fixed;
width: 100%;
text-align: right;
}

.controls {
display: inline-block;
margin: 0;
}

.button {
background: #eee;
border-radius: 2px;
border: 1px solid #aaa;
font-size: 11px;
padding: 4px 6px;
text-transform: uppercase;
}
</style>
</head>
<body>
<header class="header">
<p id="status">Loading</p>

<menu class="controls">
<button class="button" id="hydrate">Hydrate</button>
<button class="button" id="reload">Reload</button>
</menu>
</header>

<div id="output"></div>

<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
<script src="renderer.js"></script>
</body>
</html>
141 changes: 141 additions & 0 deletions fixtures/dom/public/renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Supports render.html, a piece of the hydration fixture. See /hydration
*/

'use strict';

(function() {
var Fixture = null;
var output = document.getElementById('output');
var status = document.getElementById('status');
var hydrate = document.getElementById('hydrate');
var reload = document.getElementById('reload');
var renders = 0;
var failed = false;

function getQueryParam(key) {
var pattern = new RegExp(key + '=([^&]+)(&|$)');
var matches = window.location.search.match(pattern);

if (matches) {
return decodeURIComponent(matches[1]);
}

handleError(new Error('No key found for' + key));
}

function getBooleanQueryParam(key) {
return getQueryParam(key) === 'true';
}

function setStatus(label) {
status.innerHTML = label;
}

function prerender() {
setStatus('Generating markup');

output.innerHTML = ReactDOMServer.renderToString(
React.createElement(Fixture)
);

setStatus('Markup only (No React)');
}

function render() {
setStatus('Hydrating');

if (ReactDOM.hydrate) {
ReactDOM.hydrate(React.createElement(Fixture), output);
} else {
ReactDOM.render(React.createElement(Fixture), output);
}

setStatus(renders > 0 ? 'Re-rendered (' + renders + 'x)' : 'Hydrated');
renders += 1;
hydrate.innerHTML = 'Re-render';
}

function handleError(error) {
console.log(error);
failed = true;
setStatus('Javascript Error');
output.innerHTML = error;
}

function loadScript(src) {
return new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.async = true;
script.src = src;

script.onload = resolve;
script.onerror = function(error) {
reject(new Error('Unable to load ' + src));
};

document.body.appendChild(script);
});
}

function injectFixture(src) {
Fixture = new Function(src + '\nreturn Fixture;')();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@philipp-spiess great call here. I was evaluating this in the context of the window, but I'm pretty sure we can keep it local just using a Function constructor.


if (typeof Fixture === 'undefined') {
setStatus('Failed');
output.innerHTML = 'Please name your root component "Fixture"';
} else {
prerender();

if (getBooleanQueryParam('hydrate')) {
render();
}
}
}

function reloadFixture(code) {
renders = 0;
ReactDOM.unmountComponentAtNode(output);
injectFixture(code);
}

window.onerror = handleError;

reload.onclick = function() {
window.location.reload();
};

hydrate.onclick = render;

loadScript(getQueryParam('reactPath'))
.then(function() {
return getBooleanQueryParam('needsReactDOM')
? loadScript(getQueryParam('reactDOMPath'))
: null;
})
.then(function() {
return loadScript(getQueryParam('reactDOMServerPath'));
})
.then(function() {
if (failed) {
return;
}

window.addEventListener('message', function(event) {
var data = JSON.parse(event.data);

switch (data.type) {
case 'code':
reloadFixture(data.payload);
break;
default:
throw new Error(
'Renderer Error: Unrecognized message "' + data.type + '"'
);
}
});

window.parent.postMessage(JSON.stringify({type: 'ready'}), '*');
})
.catch(handleError);
})();
4 changes: 1 addition & 3 deletions fixtures/dom/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ function App() {
return (
<div>
<Header />
<div className="container">
<Fixtures />
</div>
<Fixtures />
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion fixtures/dom/src/components/FixtureSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class FixtureSet extends React.Component {
const {title, description, children} = this.props;

return (
<div>
<div className="container">
<h1>{title}</h1>
{description && <p>{description}</p>}

Expand Down
3 changes: 2 additions & 1 deletion fixtures/dom/src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class Header extends React.Component {
<select
value={window.location.pathname}
onChange={this.handleFixtureChange}>
<option value="/">Select a Fixture</option>
<option value="/">Home</option>
<option value="/hydration">Hydration</option>
<option value="/range-inputs">Range Inputs</option>
<option value="/text-inputs">Text Inputs</option>
<option value="/number-inputs">Number Input</option>
Expand Down
2 changes: 1 addition & 1 deletion fixtures/dom/src/components/fixtures/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const React = window.React;

export default function Home() {
return (
<main>
<main className="container">
<h1>DOM Test Fixtures</h1>
<p>
Use this site to test browser quirks and other behavior that can not be
Expand Down
85 changes: 85 additions & 0 deletions fixtures/dom/src/components/fixtures/hydration/Code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const React = window.React;

export class CodeEditor extends React.Component {
shouldComponentUpdate() {
return false;
}

componentDidMount() {
// Important: CodeMirror incorrectly lays out the editor
// if it executes before CSS has loaded
// https://github.com/graphql/graphiql/issues/33#issuecomment-318188555
Promise.all([
import('codemirror'),
import('codemirror/mode/jsx/jsx'),
import('codemirror/lib/codemirror.css'),
import('./codemirror-paraiso-dark.css'),
]).then(([CodeMirror]) => this.install(CodeMirror));
}

install(CodeMirror) {
if (!this.textarea) {
return;
}

const {onChange} = this.props;

this.editor = CodeMirror.fromTextArea(this.textarea, {
mode: 'jsx',
theme: 'paraiso-dark',
lineNumbers: true,
});

this.editor.on('change', function(doc) {
onChange(doc.getValue());
});
}

componentWillUnmount() {
if (this.editor) {
this.editor.toTextArea();
}
}

render() {
return (
<textarea
ref={ref => (this.textarea = ref)}
defaultValue={this.props.code}
autoComplete="off"
hidden={true}
/>
);
}
}

/**
* Prevent IE9 from raising an error on an unrecognized element:
* See https://github.com/facebook/react/issues/13610
*/
const supportsDetails = !(
document.createElement('details') instanceof HTMLUnknownElement
);

export class CodeError extends React.Component {
render() {
const {error, className} = this.props;

if (!error) {
return null;
}

if (supportsDetails) {
const [summary, ...body] = error.message.split(/\n+/g);

return (
<details className={className}>
<summary>{summary}</summary>
{body.join('\n')}
</details>
);
}

return <div className={className}>{error.message}</div>;
}
}
18 changes: 18 additions & 0 deletions fixtures/dom/src/components/fixtures/hydration/code-transformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Babel works across all browsers, however it requires many polyfills.
*/

import 'core-js/es6/weak-map';
import 'core-js/es6/weak-set';
import 'core-js/es6/number';
import 'core-js/es6/string';
import 'core-js/es6/array';
import 'core-js/modules/es6.object.set-prototype-of';

import {transform} from '@babel/standalone';

const presets = ['es2015', 'stage-3', 'react'];

export function compile(raw) {
return transform(raw, {presets}).code;
}
Loading