diff --git a/.eslintignore b/.eslintignore index ff8a5577..705b4afe 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,7 @@ node_modules/* content/* # Ignore built files -public/* \ No newline at end of file +public/* + +# Ignore examples +codepen/* \ No newline at end of file diff --git a/codepen/components-and-props/composing-components.js b/codepen/components-and-props/composing-components.js new file mode 100644 index 00000000..9158dd0c --- /dev/null +++ b/codepen/components-and-props/composing-components.js @@ -0,0 +1,18 @@ +function Welcome(props) { + return

Hello, {props.name}

; +} + +function App() { + return ( +
+ + + +
+ ); +} + +ReactDOM.render( + , + document.getElementById('root') +); \ No newline at end of file diff --git a/codepen/components-and-props/extracting-components-continued.js b/codepen/components-and-props/extracting-components-continued.js new file mode 100644 index 00000000..bcb6547b --- /dev/null +++ b/codepen/components-and-props/extracting-components-continued.js @@ -0,0 +1,52 @@ +function formatDate(date) { + return date.toLocaleDateString(); +} + +function Avatar(props) { + return ( + {props.user.name} + ); +} + +function UserInfo(props) { + return ( +
+ +
+ {props.user.name} +
+
+ ); +} + +function Comment(props) { + return ( +
+ +
+ {props.text} +
+
+ {formatDate(props.date)} +
+
+ ); +} + +const comment = { + date: new Date(), + text: 'I hope you enjoy learning React!', + author: { + name: 'Hello Kitty', + avatarUrl: 'http://placekitten.com/g/64/64' + } +}; +ReactDOM.render( + , + document.getElementById('root') +); \ No newline at end of file diff --git a/codepen/components-and-props/extracting-components.js b/codepen/components-and-props/extracting-components.js new file mode 100644 index 00000000..720624ea --- /dev/null +++ b/codepen/components-and-props/extracting-components.js @@ -0,0 +1,40 @@ +function formatDate(date) { + return date.toLocaleDateString(); +} + +function Comment(props) { + return ( +
+
+ {props.author.name} +
+ {props.author.name} +
+
+
+ {props.text} +
+
+ {formatDate(props.date)} +
+
+ ); +} + +const comment = { + date: new Date(), + text: 'I hope you enjoy learning React!', + author: { + name: 'Hello Kitty', + avatarUrl: 'http://placekitten.com/g/64/64' + } +}; +ReactDOM.render( + , + document.getElementById('root') +); \ No newline at end of file diff --git a/codepen/components-and-props/rendering-a-component.js b/codepen/components-and-props/rendering-a-component.js new file mode 100644 index 00000000..d42e1681 --- /dev/null +++ b/codepen/components-and-props/rendering-a-component.js @@ -0,0 +1,9 @@ +function Welcome(props) { + return

Hello, {props.name}

; +} + +const element = ; +ReactDOM.render( + element, + document.getElementById('root') +); \ No newline at end of file diff --git a/codepen/hello-world.js b/codepen/hello-world.js new file mode 100644 index 00000000..d0f87a59 --- /dev/null +++ b/codepen/hello-world.js @@ -0,0 +1,4 @@ +ReactDOM.render( +

Hello, world!

, + document.getElementById('root') +); diff --git a/codepen/introducing-jsx.js b/codepen/introducing-jsx.js new file mode 100644 index 00000000..adb32664 --- /dev/null +++ b/codepen/introducing-jsx.js @@ -0,0 +1,19 @@ +function formatName(user) { + return user.firstName + ' ' + user.lastName; +} + +const user = { + firstName: 'Harper', + lastName: 'Perez', +}; + +const element = ( +

+ Hello, {formatName(user)}! +

+); + +ReactDOM.render( + element, + document.getElementById('root') +); diff --git a/content/docs/components-and-props.md b/content/docs/components-and-props.md index 1bd1f7a6..b6e967c7 100644 --- a/content/docs/components-and-props.md +++ b/content/docs/components-and-props.md @@ -76,7 +76,7 @@ ReactDOM.render( ); ``` -[Try it on CodePen.](http://codepen.io/gaearon/pen/YGYmEG?editors=0010) +Try it on CodePen. Let's recap what happens in this example: @@ -118,7 +118,7 @@ ReactDOM.render( ); ``` -[Try it on CodePen.](http://codepen.io/gaearon/pen/KgQKPr?editors=0010) +Try it on CodePen. Typically, new React apps have a single `App` component at the very top. However, if you integrate React into an existing app, you might start bottom-up with a small component like `Button` and gradually work your way to the top of the view hierarchy. @@ -152,7 +152,7 @@ function Comment(props) { } ``` -[Try it on CodePen.](http://codepen.io/gaearon/pen/VKQwEo?editors=0010) +Try it on CodePen. It accepts `author` (an object), `text` (a string), and `date` (a date) as props, and describes a comment on a social media website. @@ -231,7 +231,7 @@ function Comment(props) { } ``` -[Try it on CodePen.](http://codepen.io/gaearon/pen/rrJNJY?editors=0010) +Try it on CodePen. Extracting components might seem like grunt work at first, but having a palette of reusable components pays off in larger apps. A good rule of thumb is that if a part of your UI is used several times (`Button`, `Panel`, `Avatar`), or is complex enough on its own (`App`, `FeedStory`, `Comment`), it is a good candidate to be a reusable component. diff --git a/content/docs/hello-world.md b/content/docs/hello-world.md index 549718b1..29c0f839 100644 --- a/content/docs/hello-world.md +++ b/content/docs/hello-world.md @@ -12,7 +12,7 @@ redirect_from: - "docs/getting-started-zh-CN.html" --- -The easiest way to get started with React is to use [this Hello World example code on CodePen](http://codepen.io/gaearon/pen/ZpvBNJ?editors=0010). You don't need to install anything; you can just open it in another tab and follow along as we go through examples. If you'd rather use a local development environment, check out the [Installation](/docs/installation.html) page. +The easiest way to get started with React is to use this Hello World example code on CodePen. You don't need to install anything; you can just open it in another tab and follow along as we go through examples. If you'd rather use a local development environment, check out the [Installation](/docs/installation.html) page. The smallest React example looks like this: diff --git a/content/docs/introducing-jsx.md b/content/docs/introducing-jsx.md index b1ce9f18..11dabf04 100644 --- a/content/docs/introducing-jsx.md +++ b/content/docs/introducing-jsx.md @@ -46,7 +46,7 @@ ReactDOM.render( ); ``` -[Try it on CodePen.](http://codepen.io/gaearon/pen/PGEjdG?editors=0010) +Try it on CodePen. We split JSX over multiple lines for readability. While it isn't required, when doing this, we also recommend wrapping it in parentheses to avoid the pitfalls of [automatic semicolon insertion](http://stackoverflow.com/q/2846283). diff --git a/gatsby-node.js b/gatsby-node.js index 2cbd1ffd..359be801 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -6,6 +6,8 @@ 'use strict'; +const recursiveReaddir = require('recursive-readdir'); +const {readFileSync} = require('fs'); const {resolve} = require('path'); const webpack = require('webpack'); @@ -165,6 +167,24 @@ exports.createPages = async ({graphql, boundActionCreators}) => { redirectInBrowser: true, toPath: newestBlogNode.fields.slug, }); + + // Create Codepen redirects. + // These use the Codepen prefill API to JIT-create Pens. + // https://blog.codepen.io/documentation/api/prefill/ + const files = await recursiveReaddir('./codepen'); + files.forEach(file => { + const slug = file.substring(0, file.length - 3); // Trim extension + const code = readFileSync(file, 'utf8'); + + createPage({ + path: slug, + component: resolve('./src/templates/codepen-example.js'), + context: { + code, + slug, + }, + }); + }); }; // Parse date information out of blog post filename. diff --git a/package.json b/package.json index 9adc56da..82a862d6 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "reset": "rimraf ./.cache" }, "devDependencies": { - "eslint-config-prettier": "^2.6.0" + "eslint-config-prettier": "^2.6.0", + "recursive-readdir": "^2.2.1" } } diff --git a/src/templates/codepen-example.js b/src/templates/codepen-example.js new file mode 100644 index 00000000..615b8f7e --- /dev/null +++ b/src/templates/codepen-example.js @@ -0,0 +1,73 @@ +'use strict'; + +import React, {Component} from 'react'; +import Container from 'components/Container'; +import {colors} from 'theme'; + +const EXTERNALS = [ + 'https://unpkg.com/react/umd/react.development.js', + 'https://unpkg.com/react-dom/umd/react-dom.development.js', +]; + +// Copied over styles from ButtonLink for the submit btn +const primaryStyle = { + backgroundColor: colors.brand, + color: colors.black, + padding: '10px 25px', + whiteSpace: 'nowrap', + transition: 'background-color 0.2s ease-out', + outline: 0, + border: 'none', + cursor: 'pointer', + + ':hover': { + backgroundColor: colors.white, + }, + + display: 'inline-block', + fontSize: 16, +}; + +class CodepenExample extends Component { + componentDidMount() { + this.codepenForm.submit(); + } + + render() { + // Codepen configuration. + // https://blog.codepen.io/documentation/api/prefill/ + const payload = JSON.stringify({ + editors: '0010', + html: '
', + js: this.props.pathContext.code, + js_external: EXTERNALS.join(';'), + js_pre_processor: 'babel', + layout: 'left', + title: 'reactjs.org example', + }); + + return ( + +

Redirecting to Codepen...

+
{ + this.codepenForm = form; + }} + action="https://codepen.io/pen/define" + method="POST"> + + +

+ Not automatically redirecting? +
+
+ +

+
+
+ ); + } +} + +export default CodepenExample; diff --git a/yarn.lock b/yarn.lock index a7cd0524..c527a80a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1535,7 +1535,7 @@ bowser@^1.6.0: version "1.7.1" resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.1.tgz#a4de8f18a1a0dc9531eb2a92a1521fb6a9ba96a5" -brace-expansion@^1.1.7: +brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" dependencies: @@ -6367,6 +6367,12 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" +minimatch@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" + dependencies: + brace-expansion "^1.0.0" + minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -8054,6 +8060,12 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +recursive-readdir@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.1.tgz#90ef231d0778c5ce093c9a48d74e5c5422d13a99" + dependencies: + minimatch "3.0.3" + redbox-react@^1.3.6: version "1.5.0" resolved "https://registry.yarnpkg.com/redbox-react/-/redbox-react-1.5.0.tgz#04dab11557d26651bf3562a67c22ace56c5d3967"