# React Extension trial
## Intro
The goal here is to explore webpack ability to pass from JSX with ES6 way of handling import/export into an AMD "compliant" bundle. The idea behind is to find a way to make widgets using React on front end as an alternative of ipywidgets library and its uses of Backbone framework.
## setting up
### general set up
To begin with, I suggest that you create a dedicated space for your trial to live in. The reason why is that by the end of this cookbook, we will have created a bunch of configuration files, code sources, and production code. In addition, we will use Node Package Manager, to import React, babel and webpack. Make sure you have NPM or even better, yarn installed.
> Note: make sure your package manager is up to date
So let's create a basic set up for a node package
```bash
mkdir react_extension
cd react_extension
yarn init
mkdir src dist
touch src/index.js
```
### Installing Webpack
Webpack is a bundler, it fetches source code, and creates an output that is simpler to manipulate. That's its whole purpose, but by binding webpack with some other services such as babel, you can minify it, or even transpile it into different version of JavaScript, and this is mandatory to be browser compliant. So let's install webpack as a development dependency:
```bash
yarn add webpack webpack-cli --dev
```
To work with webpack, it needs a configuration files, called webpack.config.js. It's a quiet simple, following the documentation, we will provide a first iteration:
```bash
touch webpack.config.js
```
and editing webpack.config.js
```javascript
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
};
```
What we are doing in this configuration, is simply read from entry point index.js in src folder, and create an output in dist folder. So for now, it is not very useful... It will soon be though!
Now we need to automate the bundling, by making scripts into our package.json.
Let's add a watch, dev and build script to our project:
within your project package.json:
```javascript
"scripts": {
"build": "webpack -p",
"watch": "webpack --watch",
"dev": "webpack --mode development"
}
```
Now if you run:
```bash
yarn build && ls dist/
```
should give this output:
```bash
...
[webpack output]
...
main.js
```
I don't really want to cover all webpack options here, but just remember that
-p stands for production, which is minimized to be as light as possible. but it
isn't the best way to debug your code. To do so we have the development
bundling command which is dev script, that can be sometime useful. Yet the best
one would definitely be the watch script, that will automatically rebundle your
code every time it detects a change on one of the sources files involved.
## Adding React to the equation
Having nicely set up our project, we can now enter the heart of the matter. In this section we will install React, make a very simple React Component to render, and render it into our notebook.
### Install React
```bash
yarn add react react-dom
```
Once installed, we can start to write some code and try to build it.
### Writing Code
We need to modify our `index.js` file to look like the code below:
```javascript
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(, document.getElementById('notebook-container'))
```
What we are doing here is rendering an App component into the notebook
container element, which has the id `notebook-container`.
Anyway, we don't have an App yet, so we need to create it. In the same src folder create `App.js` and add the following to it:
```javascript
import React from 'react'
const App = () => {
return
Hello, World!
}
export default App
```
And its about time to build! At this point `yarn build` will unfortunately crash miserably... Next step: be able to build!
### Bundling
According to Webpack error messages, we need a transpiler to understand JSX, which is React's javascript flavour. So we need to call Babel to the rescue.
Below are the commands to install babel:
```bash
yarn add @babel/core babel-loader @babel/preset-env @babel/preset-react --dev
touch .babelrc
```
Then, editing your `.babelrc`
```javascript
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
```
Let's update `webpack.config.js`, it should then looks like this:
```javascript
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
}
};
```
you can now hit `yarn build` again, and it will build.
>Note: For more about setting up React from scratch using webpack and Babel, see [this very fine article](https://www.valentinog.com/blog/babel/).
## Live testing
Now we want to be able to test it very quick within a notebook. So we have to turn this into an extension.
To do so, we will use asynchronous imports from ES6 syntax. Let's change our `src/index.js` file:
```javascript
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
let promise = import('base/js/namespace')
export const load_ipython_extension = () => {
promise.then(Jupyter => {
console.log("Hello from React")
ReactDOM.render(, document.getElementById('notebook-container'))
})
}
```
Let's build:
```bash
yarn build
```
It fails with this error:
```
ERROR in ./src/index.js
Module not found: Error: Can't resolve 'base/js/namespace' in '/home/al/essai/react_extension/src'
@ ./src/index.js 4:14-41
[...more warning...]
```
Here the problem is that webpack can't find the `base/js/namespace` dependency.
We won't provide the dependency because it will be provided by Jupyter. We tell
webpack that it's fine using the
[externals](https://webpack.js.org/configuration/externals/) option. We also make it compile the code as an AMD module, which the format supported by Jupyter Notebook.
```javascript
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
// Compile as an AMD (RequireJS-style) module
libraryTarget: 'amd'
},
module:{
rules:[
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
},
// This will be available on the page
externals: ['base/js/namespace']
};
```
At this point, `yarn build` should work.
Now we'd like to test what we've done within a notebook. Let's install and enable the extension:
```bash
cd ..
source venv/bin/activate
jupyter nbextension install dist/ --sys-prefix --symlink
jupyter nbextension enable dist/main --sys-prefix
jupyter notebook
```
Now if we create a notebook, we should see *Hello from React* in the JavaScript console and we should
see *Hello, World!* in the notebook:
![](_static/hello-react.png)
We've just built our first Notebook extension with React.