ff-stream-web

git clone git://archive.git.mtrnord.blog/MTRNord/ff-stream-web.git
Log | Files | Refs | README | LICENSE

commit e772c445e0b3f954ac88f3775fb3c4a2aef6962b
parent ed372648f1283b1fdc685f76f93b4dea2e21da09
Author: Konstantin Tarkus <koistya@gmail.com>
Date:   Thu, 17 Dec 2015 15:20:10 +0300

Merge pull request #352 from koistya/fetch

Replace Superagent with WHATWG Fetch
Diffstat:
MCHANGELOG.md | 2++
Mdocs/README.md | 1+
Adocs/data-fetching.md | 32++++++++++++++++++++++++++++++++
Mdocs/recipes/how-to-implement-routing.md | 16++++++++++------
Mpackage.json | 3++-
Msrc/components/Html/Html.js | 4++--
Msrc/config.js | 6+++---
Dsrc/core/HttpClient.js | 37-------------------------------------
Asrc/core/fetch/fetch.client.js | 8++++++++
Asrc/core/fetch/fetch.server.js | 22++++++++++++++++++++++
Asrc/core/fetch/package.json | 6++++++
Msrc/routes.js | 5+++--
Msrc/server.js | 3+--
13 files changed, 92 insertions(+), 53 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ### [Unreleased][unreleased] +- Replace Superagent with WHATWG Fetch library +- Rename `app.js` to `client.js` (aka client-side code) - Integrate [CSS Modules](https://github.com/css-modules/css-modules) and [isomorphic-style-loader](https://github.com/kriasoft/isomorphic-style-loader) - Move `DOMUtils.js` to `src/core` folder; remove `src/utils` folder diff --git a/docs/README.md b/docs/README.md @@ -4,6 +4,7 @@ * [React Style Guide](./react-style-guide.md) * [How to configure text editors and IDEs](./how-to-configure-text-editors.md) +* [Data fetching with WHATWG Fetch](./data-fetching.md) ### Questions diff --git a/docs/data-fetching.md b/docs/data-fetching.md @@ -0,0 +1,32 @@ +## Data Fetching with WHATWG Fetch + +There is isomorphic `core/fetch` module that can be used the same way in both +client-side and server-side code as follows: + +```jsx +import fetch from '../core/fetch'; + +export const path = '/products'; +export const action = async () => { + const response = await fetch('/api/products'); + const data = await response.json(); + return <Layout><Products {...data}</Layout>; +}; +``` + +When this code executes on the client, the Ajax request will be sent via +GitHub's [fetch](https://github.com/github/fetch) library (`whatwg-fetch`), +that itself uses XHMLHttpRequest behind the scene unless `fetch` is supported +natively by the user's browser. + +Whenever the same code executes on the server, it uses +[node-fetch](https://github.com/bitinn/node-fetch) module behind the scene that +itself sends an HTTP request via Node.js `http` module. It also converts +relative URLs to absolute (see `./core/fetch/fetch.server.js`). + +Both `whatwg-fetch` and `node-fetch` modules have almost identical API. If +you're new to this API, the following article may give you a good introduction: + +https://jakearchibald.com/2015/thats-so-fetch/ + + diff --git a/docs/recipes/how-to-implement-routing.md b/docs/recipes/how-to-implement-routing.md @@ -51,7 +51,7 @@ Just wrap React components inside your routes into asynchronous functions: ```js import React from 'react'; -import http from './core/http'; +import fetch from './core/fetch'; import Layout from './components/Layout'; import HomePage from './components/HomePage'; import AboutPage from './components/AboutPage'; @@ -60,11 +60,13 @@ import ErrorPage from './components/ErrorPage'; const routes = { '/': async () => { - const data = await http.get('/api/data/home'); + const response = await fetch('/api/data/home'); + const data = await response.json(); return <Layout><HomePage {...data} /></Layout> }, '/about': async () => { - const data = await http.get('/api/data/about'); + const response = await fetch('/api/data/about'); + const data = await response.json(); return <Layout><AboutPage {...data} /></Layout>; } }; @@ -97,7 +99,7 @@ for matching URL paths to React components. ```js import React from 'react'; import Router from 'react-routing/src/Router'; -import http from './core/http'; +import fetch from './core/fetch'; import Layout from './components/Layout'; import ProductListing from './components/ProductListing'; import ProductInfo from './components/ProductInfo'; @@ -106,11 +108,13 @@ import ErrorPage from './components/ErrorPage'; const router = new Router(on => { on('/products', async () => { - const data = await http.get('/api/products'); + const response = await fetch('/api/products'); + const data = await response.json(); return <Layout><ProductListing {...data} /></Layout> }); on('/products/:id', async (req) => { - const data = await http.get(`/api/products/${req.params.id}`); + const response = await fetch(`/api/products/${req.params.id}`); + const data = await response.json(); return <Layout><ProductInfo {...data} /></Layout>; }); }]); diff --git a/package.json b/package.json @@ -16,12 +16,13 @@ "front-matter": "2.0.1", "history": "1.16.0", "jade": "1.11.0", + "node-fetch": "1.3.3", "normalize.css": "3.0.3", "react": "0.14.3", "react-dom": "0.14.3", "react-routing": "0.0.6", "source-map-support": "0.4.0", - "superagent": "1.6.1" + "whatwg-fetch": "0.10.1" }, "devDependencies": { "assets-webpack-plugin": "^3.2.0", diff --git a/src/components/Html/Html.js b/src/components/Html/Html.js @@ -1,7 +1,7 @@ /*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */ import React, { Component, PropTypes } from 'react'; -import config from '../../config'; +import { googleAnalyticsId } from '../../config'; class Html extends Component { @@ -25,7 +25,7 @@ class Html extends Component { `e=o.createElement(i);r=o.getElementsByTagName(i)[0];` + `e.src='https://www.google-analytics.com/analytics.js';` + `r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));` + - `ga('create','${config.googleAnalyticsId}','auto');ga('send','pageview');`, + `ga('create','${googleAnalyticsId}','auto');ga('send','pageview');`, }); } diff --git a/src/config.js b/src/config.js @@ -1,5 +1,5 @@ /*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */ -export default { - googleAnalyticsId: 'UA-XXXXX-X', -}; +export const port = process.env.PORT || 5000; +export const host = process.env.WEBSITE_HOSTNAME || `localhost:${port}`; +export const googleAnalyticsId = 'UA-XXXXX-X'; diff --git a/src/core/HttpClient.js b/src/core/HttpClient.js @@ -1,37 +0,0 @@ -/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */ - -import request from 'superagent'; -import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; - -function getUrl(path) { - if (path.startsWith('http') || canUseDOM) { - return path; - } - - return process.env.WEBSITE_HOSTNAME ? - `http://${process.env.WEBSITE_HOSTNAME}${path}` : - `http://127.0.0.1:${global.server.get('port')}${path}`; -} - -const HttpClient = { - - get: path => new Promise((resolve, reject) => { - request - .get(getUrl(path)) - .accept('application/json') - .end((err, res) => { - if (err) { - if (err.status === 404) { - resolve(null); - } else { - reject(err); - } - } else { - resolve(res.body); - } - }); - }), - -}; - -export default HttpClient; diff --git a/src/core/fetch/fetch.client.js b/src/core/fetch/fetch.client.js @@ -0,0 +1,8 @@ +/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */ + +import 'whatwg-fetch'; + +export default self.fetch.bind(self); +export const Headers = self.Headers; +export const Request = self.Request; +export const Response = self.Response; diff --git a/src/core/fetch/fetch.server.js b/src/core/fetch/fetch.server.js @@ -0,0 +1,22 @@ +/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */ + +import fetch, { Request, Headers, Response } from 'node-fetch'; +import { host } from '../../config'; + +function localUrl(url) { + if (url.startsWith('//')) { + return 'https:' + url; + } + + if (url.startsWith('http')) { + return url; + } + + return `http://${host}${url}`; +} + +function localFetch(url, options) { + return fetch(localUrl(url), options); +} + +export { localFetch as default, Request, Headers, Response }; diff --git a/src/core/fetch/package.json b/src/core/fetch/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "name": "fetch", + "main": "./fetch.server.js", + "browser": "./fetch.client.js" +} diff --git a/src/routes.js b/src/routes.js @@ -2,7 +2,7 @@ import React from 'react'; import Router from 'react-routing/src/Router'; -import http from './core/HttpClient'; +import fetch from './core/fetch'; import App from './components/App'; import ContentPage from './components/ContentPage'; import ContactPage from './components/ContactPage'; @@ -24,7 +24,8 @@ const router = new Router(on => { on('/register', async () => <RegisterPage />); on('*', async (state) => { - const content = await http.get(`/api/content?path=${state.path}`); + const response = await fetch(`/api/content?path=${state.path}`); + const content = await response.json(); return content && <ContentPage {...content} />; }); diff --git a/src/server.js b/src/server.js @@ -8,10 +8,9 @@ import ReactDOM from 'react-dom/server'; import Router from './routes'; import Html from './components/Html'; import assets from './assets.json'; +import { port } from './config'; const server = global.server = express(); -const port = process.env.PORT || 5000; -server.set('port', port); // // Register Node.js middleware