(Without Codepen / JSbin / Glitch / create-react-app) Sooo… how do you think we could serve up our small React client side examples through Express? What would we minimally need to do to even start a React app? →
This would normally be a straightforward task:
public
Markup
<div id="app"> </div>
Component
class MyComponent extends React.Component{
render() {
return React.createElement('h1', {}, 'hello');
}
}
ReactDOM.render(
React.createElement(MyComponent),
document.getElementById('app')
);
Hey… wait, I thought we used JSX with react. Let's change our code…
class MyComponent extends React.Component({
render() {
return (
<h1>Hello</h1>
);
}
}
ReactDOM.render(
<MyComponent />
document.getElementById('app')
);
Let's see what happens →
Uncaught SyntaxError: Unexpected token <
This is valid JSX, right? It works on Glitch and Codepen… so what do you think happened here? →
Babel is a set of tools that allows you to use new JavaScript syntax, now, even if browsers don't support it yet!!! YES. THE FUTURE IS NOW!
So when/where do we use a transformer, like babel? →
Things would be pretty easy if we could just use babel or some other transformer by including some client side JavaScript?
So… all signs point to the fact that compiling in browser is a bad idea, (sigh, yes, it is).
There's probably a reason why everyone says avoid in-browser transform… why →
So here's where things get kind of complicated. We'll need: →
This is where webpack comes in. Webpack will do two things for us:
Note that there's some intersection in functionality among webpack, grunt, and gulp (for example, they can all be used to concatenate files)
So grunt and gulp are build tools. That is, you specify some tasks, and they'll run them automatically. For example, you want your build tools to:
And… this will all be done without having to run separate commands. However, grunt and gulp are focused on files, while webpack is focused on a project. What does that exactly mean… →
Webpack requires that you specify a single entry point to your application.
main.js
or client.js
require
statements (or import statements for ES6 syntax!)…
It'll take all of these dependencies and output them as static assets based on your configuration.
Assuming we have a barebones Express app, the webpack docs recommend installing it both globally and locally in your project:
Globally (since you'll likely use for multiple projects)
npm install webpack -g
Then… install webpack as a development dependency in case you want to use a specific version.
npm install webpack --save-dev
This gives us a commandline tool, webpack (of course), that we'll use to bundle our JavaScript…
webpack --help
Of course, we'll also need to integrate babel into the mix - that was the whole point. We'll want to install the following libraries:
babel-core
- babel itselfbabel-loader
- webpack plugin for babelbabel-preset-react
- a bunch of plugins for babel that are useful for react apps (most notably, JSX transform)
Install all of these with --save-dev
since they're for building our project (not actual libraries that the app depends on).
Since we're using webpack (which relies on require
s), we'll download local versions of React rather than relying on a hosted / CDN version.
We can install with npm… remember to use --save
)
react
react-dom
Let's modify where we place our code so that we can use webpack. →
require
s so that webpack knows what to put together
PROJECT_DIR
|
+-webpack.config.js // config options for webpack
|
+-client.js // entry point into our client side code
|
+-components
|
+-MyComponent.js // a single react component
So… let's get rid of that inline JavaScript…
bundle.js
) doesn't exist yet!
<div id="app">
</div>
<script src="javascripts/bundle.js"></script>
Of course, we'll have to remember to tell webpack that our output should be javascripts/bundle.js
.
Create a directory in our project folder that will contain all of our components. Drop our MyComponent
code there. Remember to export it… to make it available when using require
.
In components/MyComponent.js
:
const React = require('react');
class MyComponent extends React.Component {
render() {
return (
<h1>Hello</h1>
);
}
}
module.exports = MyComponent;
Our entry point will just be a file in the project directory called client.js
. Within it, we can mount the component that we created to our DOM. →
require
the React librariesrequire
our component
const React = require('react');
const ReactDOM = require('react-dom');
const MyComponent = require('./components/MyComponent');
ReactDOM.render(<MyComponent />, document.getElementById('app'));
The webpack configuration file is called webpack.config.js
. This will be in the root of your project directory.
const path = require('path');
module.exports = {
mode: 'development' // or production
// configure the entry point
// where to output the bundle of static assets
// and configure the plugins / modules that we'll use
};
This is the entry point into our client side web app… (anything that this file requires will be bundled by webpack)
entry: './client.js',
Specify where the resulting JavaScript file should go… (javascripts/bundle.js
)
output: {
path: path.join(__dirname, '/public', 'javascripts'),
filename: 'bundle.js',
publicPath:'/javascripts'
},
path
- where on the file system we're writing tofilename
- name of resulting .js filepublicPath
- the path where you're serving this file from (http://localhost:3000/javascripts <-- /javascripts
)Specify our loaders (the transformations we'll be using…) →
module: {
rules: [
{
test: /\.jsx?$/,
exclude: [/node_modules/, /"app.js/],
loader: "babel-loader",
options: {
presets: ["react"]
}
}
]
},
In the root of your project folder, just run webpack… aaand hopefully we'll find a new js
file in public/javascripts
Running webpack: →
webpack
# magic
Checking if it created bundle.js: →
ls public/javascripts
# hopefully bundle.js
Ok… instead of hello, we'll change the text in our component. Let's see the output. What happened? →
bundle.js
is still the same… what do we have to do? →
webpack
Soooo… there are a lot of ways to automatically refresh the contents of bundle.js
. We're going to use one that:
We'll use webpack-dev-middleware
to do this.
Install via npm…
npm install --save-dev webpack-dev-middleware
In app.js
add the code below before the express static middleware.
if(process.env.NODE_ENV === 'development') {
// configure webpack-dev-middlware with our original webpack config
// then... "use" webpack-dev-middleware
const webpackDevMiddleware = require("webpack-dev-middleware");
const webpackConfig = require('./webpack.config.js')
const webpack = require("webpack");
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, {
publicPath:'/javascripts'
}));
}
A few things about the webpack-dev-middleware integration… →
publicPath
is the path in the url
localhost:3000/javascripts
<– /javascripts
)webpack.config.js
bundle.js
, and we'll see that it all works out fine (since the in-memory version is being used)!bundle.js
The example code is only active when there's an environment variable called NODE_ENV
present and equal to 'development'
:
if(process.env.NODE_ENV === 'development') { ... }
)
NODE_ENV=development nodemon bin/www