-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #192 from richieli/sonic-react
add sonic-react
- Loading branch information
Showing
17 changed files
with
1,116 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
## Getting started with React | ||
[![license](http://img.shields.io/badge/license-BSD3-brightgreen.svg?style=flat)](https://github.com/Tencent/VasSonic/blob/master/LICENSE) | ||
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/VasSonic/pulls) | ||
[![wiki](https://img.shields.io/badge/Wiki-open-brightgreen.svg)](https://github.com/Tencent/VasSonic/wiki) | ||
--- | ||
|
||
## How to use the demo | ||
|
||
>This demo will give you a quick start for using Sonic with React. | ||
### Dependencies | ||
Node Version > 7.0 | ||
### Installation | ||
```bash | ||
git clone https://github.com/Tencent/VasSonic.git <my-project-name> | ||
cd <my-project-name>/sonic-react | ||
npm install # Install project dependencies | ||
``` | ||
### Usage | ||
```bash | ||
npm run build # Builds the application to ./.next | ||
npm start # Start the development server | ||
``` | ||
Now you can visit http://localhost:3000/demo to view this demo using Mobile Emulation Mode in Chrome dev tools. | ||
|
||
## How to use Sonic on server-side | ||
|
||
>NOTE: This demo using Server Side Rendering (SSR) with [Redux](https://redux.js.org/), [Next.js](https://github.com/zeit/next.js/) and [Koa2](http://koajs.com/). | ||
1. Add comment tags to separate **template** and **data blocks** in html files which will be published from the server. The **data blocks** should begin with a html comment like `<!-- sonicdiff-moduleName -->` and close with `<!-- sonicdiff-moduleName-end -->`(the moduleName is custom). And the other part of the html is called **template** in Sonic. In this demo, it is implemented like the code shown below. | ||
|
||
- Below is the origin html, we will generate comment tags according to the `data-sonicdiff` attribute and the script block including `__NEXT_DATA__`: | ||
|
||
```HTML | ||
<!-- add comment tags to separate template and data blocks from the initial html --> | ||
<!DOCTYPE html> | ||
<html> | ||
<head></head> | ||
<body> | ||
… … | ||
<div id="root" data-sonicdiff="firstScreenHtml"> | ||
… … | ||
</div> | ||
… … | ||
<script> | ||
__NEXT_DATA__=xxx | ||
</script> | ||
</body> | ||
</html> | ||
``` | ||
|
||
- Then, we have a transform function at server side: | ||
|
||
```js | ||
function formatHtml(html) { | ||
const $ = cheerio.load(html); | ||
$('*[data-sonicdiff]').each(function(index, element) { | ||
let tagName = $(this).data('sonicdiff'); | ||
return $(this).replaceWith('<!--sonicdiff-' + tagName + '-->' + $(this).clone() + '<!--sonicdiff-' + tagName + '-end-->'); | ||
}); | ||
html = $.html(); | ||
html = html.replace(/<script\s*>\s*__NEXT_DATA__\s*=([\s\S]+?)<\/script>/ig, function(data1) { | ||
return '<!--sonicdiff-initState-->' + data1 + '<!--sonicdiff-initState-end-->'; | ||
}); | ||
return html; | ||
} | ||
``` | ||
- After the transform, the latest code user will received will be like: | ||
|
||
```HTML | ||
<!-- add comment tags to separate template and data blocks from the initial html --> | ||
<!DOCTYPE html> | ||
<html> | ||
<head></head> | ||
<body> | ||
… … | ||
+ <!-- sonicdiff-firstScreenHtml --> | ||
<div id="root" data-sonicdiff="firstScreenHtml"> | ||
… … | ||
</div> | ||
+ <!-- sonicdiff-firstScreenHtml-end --> | ||
… … | ||
+ <!-- sonicdiff-initState --> | ||
<script> | ||
__NEXT_DATA__=xxx | ||
</script> | ||
+ <!-- sonicdiff-initState-end --> | ||
</body> | ||
</html> | ||
``` | ||
|
||
2. Intercept the html response from server and use [sonic_differ](https://github.com/Tencent/VasSonic/blob/master/sonic-nodejs/common/diff.js) module to process the response. | ||
|
||
```js | ||
server.use(async (ctx, next) => { | ||
await next(); | ||
// only intercept html request | ||
if (!ctx.response.is('html')) { | ||
return; | ||
} | ||
// process non-sonic mode | ||
if (!ctx.request.header['accept-diff']) { | ||
ctx.body = ctx.state.resHtml; | ||
return; | ||
} | ||
// use sonic_differ module to process the response | ||
let sonicData = sonicDiff(ctx, formatHtml(ctx.state.resHtml)); | ||
if (sonicData.cache) { | ||
// 304 Not Modified, return nothing. | ||
ctx.body = ''; | ||
} else { | ||
// other Sonic status. | ||
ctx.body = sonicData.data; | ||
} | ||
}); | ||
``` | ||
|
||
For more details please refer to [server.js](https://github.com/Tencent/VasSonic/blob/master/sonic-react/server.js). | ||
|
||
## How to use Sonic on client-side | ||
|
||
Handle the response from mobile client which include Sonic response code and diff data in `componentDidMount()`. | ||
|
||
```js | ||
componentDidMount() { | ||
// handle the response from mobile client which include Sonic response code and diff data. | ||
this.getSonicData((status, sonicUpdateData) => { | ||
switch (status) { | ||
// here, we only process the case when data updates | ||
case 3: | ||
// update the Redux store based on changes from the mobile client | ||
let initState = sonicUpdateData['{initState}'] || ''; | ||
initState.replace(/<!--sonicdiff-initState-->\s*<script>\s*__NEXT_DATA__\s*=([\s\S]+?)module=/ig, function(matched, $1) { | ||
window.__NEXT_DATA__ = JSON.parse($1); | ||
}); | ||
this.props.initImgArr(window.__NEXT_DATA__.props.initialState.gameArea); | ||
break; | ||
default: | ||
break | ||
} | ||
// display sonic status | ||
this.props.setSonicStatus(status); | ||
}); | ||
} | ||
|
||
getSonicData(callback) { | ||
let sonicHadExecute = 0; // whether the callback is triggered | ||
const timeout = 3000; // a timeout to trigger callback | ||
|
||
// Interacts with mobile client by JavaScript interface to get Sonic diff data. | ||
window.sonic && window.sonic.getDiffData(); | ||
|
||
function sonicCallback(data) { | ||
if (sonicHadExecute === 0) { | ||
sonicHadExecute = 1; | ||
callback(data['sonicStatus'], data['sonicUpdateData']); | ||
} | ||
} | ||
|
||
setTimeout(function() { | ||
if (sonicHadExecute === 0) { | ||
sonicHadExecute = 1; | ||
callback(0, {}); | ||
} | ||
}, timeout); | ||
|
||
// the mobile client will invoke method getDiffDataCallback which can send Sonic response code and diff data to websites. | ||
window['getDiffDataCallback'] = function (sonicData) { | ||
/** | ||
* Sonic status: | ||
* 0: It fails to get any data from mobile client. | ||
* 1: It is first time for mobile client to use Sonic. | ||
* 2: Mobile client reload the whole websites. | ||
* 3: Websites will be updated dynamically with local refresh. | ||
* 4: The Sonic request of mobile client receives a 304 response code and nothing has been modified. | ||
*/ | ||
let sonicStatus = 0; | ||
let sonicUpdateData = {}; // sonic diff data | ||
sonicData = JSON.parse(sonicData); | ||
switch (parseInt(sonicData['srcCode'], 10)) { | ||
case 1000: | ||
sonicStatus = 1; | ||
break; | ||
case 2000: | ||
sonicStatus = 2; | ||
break; | ||
case 200: | ||
sonicStatus = 3; | ||
sonicUpdateData = JSON.parse(sonicData['result'] || '{}'); | ||
break; | ||
case 304: | ||
sonicStatus = 4; | ||
break; | ||
} | ||
sonicCallback({ sonicStatus: sonicStatus, sonicUpdateData: sonicUpdateData }); | ||
}; | ||
} | ||
``` | ||
For more details please refer to [demo.js](https://github.com/Tencent/VasSonic/blob/master/sonic-react/pages/demo.js) | ||
|
||
## Support | ||
Any problem? | ||
|
||
1. Learn more from [sample](https://github.com/Tencent/VasSonic/tree/master/sonic-react). | ||
2. Contact us for help. | ||
|
||
## License | ||
VasSonic is under the BSD license. See the [LICENSE](https://github.com/Tencent/VasSonic/blob/master/LICENSE) file for details. |
Oops, something went wrong.