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

[Documentation] hello upward tutorial #1080

Merged
merged 12 commits into from
Apr 4, 2019
6 changes: 5 additions & 1 deletion pwa-devdocs/src/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ defaults:
path: "venia-pwa-concept"
values:
toc-group: "venia-pwa-concept"

-
scope:
path: "tutorials"
values:
toc-group: "tutorials"

algolia:
application_id: 'E642SEDTHL'
Expand Down
3 changes: 3 additions & 0 deletions pwa-devdocs/src/_data/top-nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@

- label: Venia Storefront
url: /venia-pwa-concept/

- label: Tutorials
url: /tutorials/
16 changes: 16 additions & 0 deletions pwa-devdocs/src/_data/tutorials.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
title: Tutorials
entries:
- label: Overview
url: /tutorials/

- label: Hello UPWARD
entries:

- label: Creating a simple server
url: /tutorials/hello-upward/simple-server/

- label: Using the TemplateResolver
url: /tutorials/hello-upward/using-template-resolver/

- label: Adding React
url: /tutorials/hello-upward/adding-react/
5 changes: 4 additions & 1 deletion pwa-devdocs/src/_includes/layout/sidebar-nav-root-item.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
{% elsif page.toc-group == "venia-pwa-concept" %}
{% assign toc = site.data.venia-pwa-concept %}

{% elsif page.toc-group == "tutorials" %}
{% assign toc = site.data.tutorials %}

{% endif %}

{% include layout/toc.html %}
{% include layout/toc.html %}
1 change: 1 addition & 0 deletions pwa-devdocs/src/_scss/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
@import "src/material-icons";

@import "src/site-header-overrides.scss";
@import "src/code-block-style-overrides.scss";
13 changes: 13 additions & 0 deletions pwa-devdocs/src/_scss/src/_code-block-style-overrides.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.highlight .gd,
.highlight .gi {
jcalcaben marked this conversation as resolved.
Show resolved Hide resolved
border: none;
background: none;
}

.highlight .gi {
color: #00B218;
}

.highlight .gd {
color: #FF3B6F;
}
176 changes: 176 additions & 0 deletions pwa-devdocs/src/tutorials/hello-upward/adding-react/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
title: Adding React
---

This tutorial teaches you how to add webpack to your UPWARD project and use it to create a React application.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this topic can be made much simpler by getting rid of all the "extras" around React.

Since React doesn't actually need JSX you can get rid of the dependency on babel and webpack here.

Check it out:

hello-world.mst

{{> templates/open-document}}

<title>{{ title }}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

{{> templates/open-body}}

<div id="root">Loading...</div>

<script defer src="/app.js"></script>

{{> templates/close-document}}

Notice that the template is pulling in React and React DOM from CDNs. This eliminates the need to install them locally.

app.js

const App = () => {
    return React.createElement(
        'h1',
        null,
        'Hello World from React!'
    );
};

ReactDOM.render(
    React.createElement(App),
    document.getElementById('root')
);

Notice that we aren't using JSX here and instead using the React.createElement function directly. This is what Babel does for you.

The only thing left to do is fix up the UPWARD spec a bit:

status: response.status
headers: response.headers
body: response.body

response:
  resolver: conditional
  when:
      - matches: request.url.pathname
        pattern: '^/?$'
        use: helloWorld
      - matches: request.url.pathname
        pattern: '^/hello-world/?$'
        use: helloWorld
      - matches: request.url.pathname
        pattern: '^/app.js$'
        use: appScript
  default: notFound

helloWorld:
  inline:
      status:
          resolver: inline
          inline: 200
      headers:
          resolver: inline
          inline:
              content-type:
                  resolver: inline
                  inline: 'text/html'
      body:
          resolver: template
          engine: mustache
          template: './templates/hello-world.mst'
          provide:
            title:
              resolver: inline
              inline: 'This is the page title!'
notFound:
    inline:
        status:
            resolver: inline
            inline: 404
        headers:
            resolver: inline
            inline:
                content-type:
                    resolver: inline
                    inline: 'text/string'
        body:
            resolver: inline
            inline: 'Page not found!'

appScript:
  inline:
    status:
      resolver: inline
      inline: 200
    headers:
      resolver: inline
      inline:
        content-type:
          resolver: inline
          inline: 'application/javascript'
    body:
      resolver: file
      file:
        resolver: inline
        inline: './app.js'
      encoding:
        resolver: inline
        inline: 'utf-8'

Since this is an UPWARD tutorial, I think this greatly simplifies the topic and introduces readers to the FileResolver more quickly.

We probably want to include a paragraph about how production code should use webpack and babel but I don't think they need to be introduced in this tutorial.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Even though it's billed as an UPWARD tutorial, I also wanted the end result of doing this tutorial to be an environment that the reader can use as a starting point for their PWA Studio projects. This is something the community has been asking us for.

It's true that React doesn't require JSX, but most examples out there use JSX and ES6 syntax. I think it would be a bad experience for developers if we introduce how something is done in the tutorial and promote something very different in the implementation code.


This tutorial builds on the project described in the previous [Using the TemplateResolver][] topic.

## Install dependencies

Add babel, webpack, and react dependencies to the project:

```sh
yarn add @babel/core @babel/preset-env babel-loader webpack webpack-cli @babel/preset-react react react-dom
```

In addition to installing React, this command installs [babel][] configurations and [webpack][].

In general, React components are written using ES6([ECMAScript 2015][]) and [JSX][] syntax for the convenience and ease of readability they provide.
Using [babel][] and [webpack][] is a common way to incorporate ES6 and JSX into a React project.

## Modify template

Modify the `hello-world.mst` template to replace the original message with a "root" DOM node for the React application:

{% raw %}

```diff
{{> templates/open-document}}

<title>{{ title }}</title>

{{> templates/open-body}}

- <b>Hello Template World!</b>
+ <div id="root">Loading...</div>
+
+ <script defer src="/js/app.js"></script>

{{> templates/close-document}}
```

{% endraw %}

This update renders a loading message in the root DOM and executes the `app.js` script after the browser parses the HTML document.

## Create webpack config

Create a `webpack.config.js` file to configure webpack behavior:

```js
const path = require('path');

module.exports = {
mode: 'development',
entry: './src/hello-world.js',
output: {
filename: 'app.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
};
```

This configuration file tells webpack to transpile all JavaScript files using babel and bundle `src/hello-world.js` with its dependencies into `dist/app.js`.

## Create babel config

Create a `.babelrc` file to configure babel:

```text
{
"presets": ["@babel/preset-env","@babel/preset-react"]
}
```

This configuration file tells babel what presets to use during JavaScript transpilation.
The presets used in this example, `@babel/preset-env` and `@babel/preset-react`, are common React development presets.

## Create the React application

Create a `src` directory in your project's root directory and a `hello-world.js` file inside that directory with the following content:

```jsx
import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
render() {
return <h1>Hello React World!</h1>;
}
}

ReactDOM.render(<App />, document.getElementById('root'));
```

This file defines an `App` React component that returns a "Hello React World!" message wrapped inside `h1` tags.
It uses the `ReactDOM` module to inject the content of the application into the "root" DOM element in the document.

## Make the application script publicly available

Edit the `spec.yml` file to add a new URL pattern to check in the ConditionalResolver and a new `appScript` object.

```diff
...

response:
resolver: conditional
when:
- matches: request.url.pathname
pattern: '^/?$'
use: helloWorld
- matches: request.url.pathname
pattern: '^/hello-world/?$'
use: helloWorld
+ - matches: request.url.pathname
+ pattern: '^/js/app.js'
+ use: appScript
default: notFound

helloWorld:
...

notFound:
...

+ appScript:
+ inline:
+ status:
+ resolver: inline
+ inline: 200
+ headers:
+ resolver: inline
+ inline:
+ content-type:
+ resolver: inline
+ inline: 'application/javascript'
+ body:
+ resolver: file
+ file:
+ resolver: inline
+ inline: './dist/app.js'
+ encoding:
+ resolver: inline
+ inline: 'utf-8'
```

This update uses a [FileResolver][] which instructs the UPWARD server to respond with the contents of `dist/app.js` when there is a request for the `js/app.js` path.

## Create the bundle and start the server

Create the `dist/app.js` bundle from `src/hello-world.js` and start the UPWARD server:

```sh
npx webpack && node server.js
```

When webpack bundles `src/hello-world.js` into `dist/app.js`, it includes its dependencies, such as React and ReactDOM.

When you navigate to the server, you will see the React application render the "Hello React World!" message using the App component.

[Using the TemplateResolver]: {{site.baseurl}}{%link tutorials/hello-upward/using-template-resolver/index.md %}

[ecmascript 2015]: http://www.ecma-international.org/ecma-262/6.0/index.html
[jsx]: https://reactjs.org/docs/introducing-jsx.html
[babel]: https://babeljs.io/
[webpack]: https://webpack.js.org/
[FileResolver]: https://github.com/magento-research/pwa-studio/tree/develop/packages/upward-spec#fileresolver
Loading