This demo will give you a quick start for using Sonic with React.
Node Version > 7.0
git clone https://github.com/Tencent/VasSonic.git <my-project-name>
cd <my-project-name>/sonic-react
npm install # Install project dependencies
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.
NOTE: This demo using Server Side Rendering (SSR) with Redux, Next.js and Koa2.
- 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__
:
<!-- 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:
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:
<!-- 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>
- Intercept the html response from server and use sonic_differ module to process the response.
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.
Handle the response from mobile client which include Sonic response code and diff data in componentDidMount()
.
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
Any problem?
- Learn more from sample.
- Contact us for help.
VasSonic is under the BSD license. See the LICENSE file for details.