Upgrade React guides for react-scripts 5.0.0 (#19)

This commit is contained in:
Franck R 2022-01-10 12:11:15 +11:00 committed by GitHub
parent e3b7cf9a3d
commit 10be3e4e7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 432 additions and 45 deletions

View File

@ -17,8 +17,140 @@ For this guide, we are using a single content topic: `/min-react-js-chat/1/chat/
Create a new React app: Create a new React app:
```shell ```shell
npx create-react-app min-react-js-chat npx create-react-app relay-reactjs-chat
cd min-react-js-chat cd relay-reactjs-chat
```
## `BigInt`
Some of js-waku's dependencies use [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
that is [only supported by modern browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility).
To ensure that `react-scripts` properly transpile your webapp code, update the `package.json` file:
```json
{
"browserslist": {
"production": [
">0.2%",
"not ie <= 99",
"not android <= 4.4.4",
"not dead",
"not op_mini all"
]
}
}
```
## Setup polyfills
A number of Web3 dependencies need polyfills.
Said polyfills must be explicitly declared when using webpack 5.
The latest `react-scripts` version uses webpack 5.
We will describe below a method to configure polyfills when using `create-react-app`/`react-scripts` or webpack 5.
This may not be necessary if you do not use `react-scripts` or if you use webpack 4.
Start by installing the polyfill libraries:
```shell
npm install assert buffer crypto-browserify stream-browserify
```
### Webpack 5
If you directly use webpack 5,
then you can inspire yourself from this [webpack.config.js](https://github.com/status-im/wakuconnect-vote-poll-sdk/blob/main/examples/mainnet-poll/webpack.config.js).
### cra-webpack-rewired
An alternative is to let `react-scripts` control the webpack 5 config and only override some elements using `cra-webpack-rewired`.
Install `cra-webpack-rewired`:
```shell
npm install -D cra-webpack-rewired
```
Create a `config/webpack.extend.js` file at the root of your app:
```js
const webpack = require('webpack');
module.exports = {
dev: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('dev'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
prod: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('prod'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
};
```
Use `cra-webpack-rewired` in the `package.json`, instead of `react-scripts`:
```
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
- "eject": "react-scripts eject"
+ "start": "cra-webpack-rewired start",
+ "build": "cra-webpack-rewired build",
+ "test": "cra-webpack-rewired test",
+ "eject": "cra-webpack-rewired eject"
},
``` ```
Then, install [js-waku](https://npmjs.com/package/js-waku): Then, install [js-waku](https://npmjs.com/package/js-waku):
@ -33,15 +165,6 @@ Start the dev server and open the dApp in your browser:
npm run start npm run start
``` ```
Note: We have noticed some [issues](https://github.com/status-im/js-waku/issues/165) with React bundling due to `npm` pulling an old version of babel.
If you are getting an error about the [optional chaining (?.)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
character not being valid, try cleaning up and re-installing your dependencies:
```shell
rm -rf node_modules package-lock.json
npm install
```
# Create Waku Instance # Create Waku Instance
In order to interact with the Waku network, you first need a Waku instance. In order to interact with the Waku network, you first need a Waku instance.
@ -76,12 +199,13 @@ function App() {
return ( return (
<div className='App'> <div className='App'>
<header className='App-header'> <header className='App-header'>
// Display the status on the web page <p>Waku node's status: {wakuStatus}</p>
<p>{wakuStatus}</p>
</header> </header>
</div> </div>
); );
} }
export default App;
``` ```
# Wait to be connected # Wait to be connected
@ -136,9 +260,9 @@ Create a function that takes the Waku instance and a message to send:
```js ```js
import { WakuMessage } from 'js-waku'; import { WakuMessage } from 'js-waku';
const ContentTopic = `/min-react-js-chat/1/chat/proto`; const ContentTopic = `/relay-reactjs-chat/1/chat/proto`;
function sendMessage(message, timestamp, waku) { function sendMessage(message, waku, timestamp) {
const time = timestamp.getTime(); const time = timestamp.getTime();
// Encode to protobuf // Encode to protobuf
@ -184,7 +308,7 @@ function App() {
<div className="App"> <div className="App">
<header className="App-header"> <header className="App-header">
<p>{wakuStatus}</p> <p>{wakuStatus}</p>
<button onClick={sendMessageOnClick} disabled={wakuStatus !== 'Ready'}> // Grey the button is Waku is not yet ready. <button onClick={sendMessageOnClick} disabled={wakuStatus !== 'Ready'}>
Send Message Send Message
</button> </button>
</header> </header>
@ -302,4 +426,4 @@ function App() {
And Voilà! You should now be able to send and receive messages. And Voilà! You should now be able to send and receive messages.
Try out by opening the app from different browsers. Try out by opening the app from different browsers.
You can see the complete code in the [Minimal ReactJS Chat App](https://github.com/status-im/js-waku/tree/main/examples/min-react-js-chat). You can see the complete code in the [Relay ReactJS Chat Example App](https://github.com/status-im/js-waku/tree/main/examples/relay-reactjs-chat).

View File

@ -33,8 +33,140 @@ Check out the [how to choose a content topic guide](/docs/guides/01_choose_conte
Create a new React app: Create a new React app:
```shell ```shell
npx create-react-app my-app npx create-react-app store-reactjs-chat
cd my-app cd store-reactjs-chat
```
## `BigInt`
Some of js-waku's dependencies use [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
that is [only supported by modern browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility).
To ensure that `react-scripts` properly transpile your webapp code, update the `package.json` file:
```json
{
"browserslist": {
"production": [
">0.2%",
"not ie <= 99",
"not android <= 4.4.4",
"not dead",
"not op_mini all"
]
}
}
```
## Setup polyfills
A number of Web3 dependencies need polyfills.
Said polyfills must be explicitly declared when using webpack 5.
The latest `react-scripts` version uses webpack 5.
We will describe below a method to configure polyfills when using `create-react-app`/`react-scripts` or webpack 5.
This may not be necessary if you do not use `react-scripts` or if you use webpack 4.
Start by installing the polyfill libraries:
```shell
npm install assert buffer crypto-browserify stream-browserify
```
### Webpack 5
If you directly use webpack 5,
then you can inspire yourself from this [webpack.config.js](https://github.com/status-im/wakuconnect-vote-poll-sdk/blob/main/examples/mainnet-poll/webpack.config.js).
### cra-webpack-rewired
An alternative is to let `react-scripts` control the webpack 5 config and only override some elements using `cra-webpack-rewired`.
Install `cra-webpack-rewired`:
```shell
npm install -D cra-webpack-rewired
```
Create a `config/webpack.extend.js` file at the root of your app:
```js
const webpack = require('webpack');
module.exports = {
dev: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('dev'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
prod: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('prod'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
};
```
Use `cra-webpack-rewired` in the `package.json`, instead of `react-scripts`:
```
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
- "eject": "react-scripts eject"
+ "start": "cra-webpack-rewired start",
+ "build": "cra-webpack-rewired build",
+ "test": "cra-webpack-rewired test",
+ "eject": "cra-webpack-rewired eject"
},
``` ```
Then, install [js-waku](https://npmjs.com/package/js-waku): Then, install [js-waku](https://npmjs.com/package/js-waku):
@ -49,7 +181,8 @@ Start the dev server and open the dApp in your browser:
npm run start npm run start
``` ```
Note: We have noticed some [issues](https://github.com/status-im/js-waku/issues/165) with React bundling due to `npm` pulling an old version of babel. {{< hint info >}}
We have noticed some [issues](https://github.com/status-im/js-waku/issues/165) with React bundling due to `npm` pulling an old version of babel.
If you are getting an error about the [optional chaining (?.)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) If you are getting an error about the [optional chaining (?.)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
character not being valid, try cleaning up and re-installing your dependencies: character not being valid, try cleaning up and re-installing your dependencies:
@ -57,6 +190,7 @@ character not being valid, try cleaning up and re-installing your dependencies:
rm -rf node_modules package-lock.json rm -rf node_modules package-lock.json
npm install npm install
``` ```
{{< /hint >}}
# Create Waku Instance # Create Waku Instance
@ -90,12 +224,13 @@ function App() {
return ( return (
<div className='App'> <div className='App'>
<header className='App-header'> <header className='App-header'>
// Display the status on the web page
<p>{wakuStatus}</p> <p>{wakuStatus}</p>
</header> </header>
</div> </div>
); );
} }
export default App;
``` ```
# Wait to be connected # Wait to be connected
@ -217,40 +352,43 @@ const processMessages = (retrievedMessages) => {
}; };
``` ```
Finally, pass `processMessage` in `WakuStore.queryHistory` as the `callback` value: Pass `processMessage` in `WakuStore.queryHistory` as the `callback` value:
```js ```js
waku.store waku.store
.queryHistory([ContentTopic], { callback: processMessages }); .queryHistory([ContentTopic], { callback: processMessages });
``` ```
All together, you should now have: Finally, create a `Messages` component to render the messages:
```js ```tsx
const ContentTopic = '/toy-chat/2/huilong/proto'; function Messages(props) {
return props.messages.map(({ text, timestamp, nick }) => {
return (
<li>
({formatDate(timestamp)}) {nick}: {text}
</li>
);
});
}
function formatDate(timestamp) {
return timestamp.toLocaleString([], {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
hour12: false,
});
}
```
Use `Messages` in the `App` function:
```tsx
function App() { function App() {
// [..] // [..]
// Store messages in the state
const [messages, setMessages] = React.useState([]);
React.useEffect(() => {
if (wakuStatus !== 'Connected') return;
const processMessages = (retrievedMessages) => {
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
setMessages((currentMessages) => {
return currentMessages.concat(messages.reverse());
});
};
waku.store
.queryHistory([ContentTopic], { callback: processMessages })
.catch((e) => {
console.log('Failed to retrieve messages', e);
});
}, [waku, wakuStatus]);
return ( return (
<div className='App'> <div className='App'>
@ -264,13 +402,138 @@ function App() {
</div> </div>
); );
} }
```
All together, you should now have:
```js
import { Waku } from 'js-waku';
import * as React from 'react';
import protons from 'protons';
const ContentTopic = '/toy-chat/2/huilong/proto';
const proto = protons(`
message ChatMessage {
uint64 timestamp = 1;
string nick = 2;
bytes text = 3;
}
`);
function App() {
const [waku, setWaku] = React.useState(undefined);
const [wakuStatus, setWakuStatus] = React.useState('None');
const [messages, setMessages] = React.useState([]);
// Start Waku
React.useEffect(() => {
// If Waku status not None, it means we are already starting Waku
if (wakuStatus !== 'None') return;
setWakuStatus('Starting');
// Create Waku
Waku.create({ bootstrap: true }).then((waku) => {
// Once done, put it in the state
setWaku(waku);
// And update the status
setWakuStatus('Connecting');
});
}, [waku, wakuStatus]);
React.useEffect(() => {
if (!waku) return;
if (wakuStatus === 'Connected') return;
waku.waitForConnectedPeer().then(() => {
setWakuStatus('Connected');
});
}, [waku, wakuStatus]);
React.useEffect(() => {
if (wakuStatus !== 'Connected') return;
const processMessages = (retrievedMessages) => {
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
setMessages((currentMessages) => {
return currentMessages.concat(messages.reverse());
});
};
waku.store
.queryHistory([ContentTopic], { callback: processMessages })
.catch((e) => {
console.log('Failed to retrieve messages', e);
});
}, [waku, wakuStatus]);
return (
<div className='App'>
<header className='App-header'>
<h2>{wakuStatus}</h2>
<h3>Messages</h3>
<ul>
<Messages messages={messages} />
</ul>
</header>
</div>
);
}
export default App;
function decodeMessage(wakuMessage) {
if (!wakuMessage.payload) return;
const { timestamp, nick, text } = proto.ChatMessage.decode(
wakuMessage.payload
);
// All fields in protobuf are optional so be sure to check
if (!timestamp || !text || !nick) return;
const time = new Date();
time.setTime(timestamp);
const utf8Text = Buffer.from(text).toString('utf-8');
return { text: utf8Text, timestamp: time, nick };
}
function Messages(props) {
return props.messages.map(({ text, timestamp, nick }) => {
return (
<li>
({formatDate(timestamp)}) {nick}: {text}
</li>
);
});
}
function formatDate(timestamp) {
return timestamp.toLocaleString([], {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
hour12: false,
});
}
``` ```
Note that `WakuStore.queryHistory` select an available store node for you. Note that `WakuStore.queryHistory` select an available store node for you.
However, it can only select a connected node, which is why the bootstrapping is necessary. However, it can only select a connected node, which is why the bootstrapping is necessary.
It will throw an error if no store node is available. It will throw an error if no store node is available.
If no message are returned,
then you can use https://status-im.github.io/js-waku/ to send some messages on the
toy chat topic and refresh your app.
## Filter messages by send time ## Filter messages by send time
By default, Waku Store nodes store messages for 30 days. By default, Waku Store nodes store messages for 30 days.