Setting up Webpack, Babel and React from scratch

Update, October 2017

I just released updated tutorial right here. So feel free to skip this one, and read a new one. It uses updated tools, and hopefully it will grow into a new series of webpack/react posts.

Update, March 2017

Webpack 2 is out, so this post is slowly becoming outdated. For webpack 2 - react boilerplate please check this post.

This is a living guide

This is the first part of the guide that will be changed over time. For now it covers Webpack, Babel (for ES6) and React with React Router.

Next parts will contain more stuff - static properties, decorators, SASS, development and production configs, immutables... Also redux part should be updated really soon.

So stay tuned!

Other parts:

  • Part 1 - Webpack, Babel, React, Router, ESLint
  • Part 2 - SASS, more ES6 goodness (Static props, decorators, deconstruction...)
  • Part 3 - Where to go from here

Before we start

I'll assume that you have a basic knowledge of the unix terminal, and that you have read what Webpack, Babel and React are.

Webpack

For a start, install node and npm from https://nodejs.org/en/.

Make a git repo (this is optional, but recommended), or create an empty folder. Navigate to it in the terminal.

Initialize npm (package.json) by running

npm init

Now we can start adding npm packages. Install babel core and it's loader for webpack.

npm install --save-dev webpack webpack-dev-server

Tip: you can use npm i instead of npm install.

Create app/js/app.js with a simple console.log('hello world');. This will be the entry point for webpack.

Now we need to create a webpack config file webpack.config.js.

Tip: you can create files by using touch command - touch FILENAME

module.exports = {
  context: __dirname + "/app",

  entry: "./js/app.js",

  output: {
    filename: "app.js",
    path: __dirname + "/dist",
  }
};

It is important to understand what is going on so far. This tells webpack that our main application file (app.js) is the entry point, and bundled application should be outputted to the dist folder.

__dirname is the name of the directory that the currently executing script resides in.

Now we can run

node ./node_modules/webpack/bin/webpack.js

It will generate dist/app.js. (Tip: to be able to run webpack globally, you'll need to install it using npm i --global webpack. Then you can run it by using only webpack.)

Babel

Noew can add Babel transpiler goodness.

Install babel core and it's loader for webpack and presets for ES6 (aka ES2015) and React

npm i --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react

Create .babelrc in the project root folder and add presets

{
  "presets": [
    "es2015",
    "react"
  ]
}

Add js/jsx loader to your webpack config, as well as extensions we want to resolve (More about this later).

...
  resolve: {
    extensions: ['', '.js', '.jsx', '.json']
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loaders: ["babel-loader"]
      }
    ]
  }
...

Webpack accepts the array of the loaders. Loader has a test for the filenames, in our case it matches all of the .js and .jsx files. Then it applies babel loader to it. Basically this will transpile our fancy ES6 to ES5 which can be understood by browsers (some browsers can execute ES6 already, but most of them still can't).

If you re-run our webpack command, nothing will change, yet.

React

Install react and react DOM

npm i react react-dom --save

Your app.js should look something like this. For this example we are just rendering main menu. Later we'll replace that with react router component.

import React from 'react';
import ReactDOM from 'react-dom';
import Menu from './components/Global/Menu.jsx';

ReactDOM.render(
  <Menu />,
  document.getElementById('app')
);

Now we can start adding react components. Create app/js/components/Global/Menu.jsx

import React, { Component } from 'react';

export default class Menu extends Component {
  render() {
    return (
      <div className='Menu'>
        Main Menu
      </div>
    );
  }
}

Index Page

Create index page in the app folder.

<!DOCTYPE html>
<html>
<head>
  <title>Your app name</title>
  <meta charset="utf-8">
</head>
<body>
  <div id="app"></div>
  <script src="app.js"></script>
</body>
</html>

We'll need file loader for it

npm install file-loader --save-dev

Update webpack config to add entry

...
  entry: {
    javascript: "./js/app.js",
    html: "./index.html",
  }
...

and add loader

...
  {
    test: /\.html$/,
    loader: "file?name=[name].[ext]",
  }
...

Now when we run webpack again, we'll get index.html and app.js in dist folder.

Dev server and hot reload

Install webpack development server, and run it

npm install --save-dev webpack webpack-dev-server
node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js

(Tip: Same as with webpack, you can install it globally npm i --global webpack-dev-server and run it using webpack-dev-server.)

Now open http://localhost:8080/ in your browser of choice. You should see your app. Development server will watch for your changes and rerun the bundler. Still we need to manually refresh the browser.

So let's add hot reloading.

npm install react-hot-loader --save-dev

Update webpack js/jsx loader to use hot reloading

...
  {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    loaders: ["react-hot", "babel-loader"],
  }
...

Now we need to add two more options when running dev server --hot and --inline. We will also add --history-api-fallback which will be useful when we add react router. This option tells server to fallback to index.html which will handle routing (as we are building single page app).

node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --hot --inline

Not only the hot reload watches the changes and updates the browser, but it injects the code and keeps the application state. Note that not all modules can be replaced. The code in app/js/app.js cannot be reloaded and will cause a full page reload but changing the children components will trigger a hot module replacement.

To make things easier, we'll add our script to the package.json file.

...
  "scripts": {
    "dev": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --hot --inline --history-api-fallback"
  }
...

Now we can run only npm run dev

ESLint

We are going to use AirBNB's set of rules for linting, as their standards are really good. Also, creating eslint by hand would take a long time.

Pro tip: ALWAYS LINT. No matter what people say - always use linters. You will catch errors earlier, and enforce your (the best) standards.

https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb

npm install --save-dev eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y eslint

Now we need to create .eslintrc that only needs extends param, but if you want to customize it, add your rules to the rules object. Personally I like single quotes more, and spaces in the react curly synthax.

You might have to restart your editor for changes to take effect. For beginners I would recommend Atom editor.

{
  "extends": "airbnb",
  "rules": {
    "jsx-quotes": [2, "prefer-single"],
    "react/jsx-curly-spacing": [2, "always"],
    "react/prefer-stateless-function": [0]
  }
}

React Router

Install it from the NPM

npm install --save react-router

What we'll do is pretty much the same thing from the official getting started guide.

https://github.com/reactjs/react-router/blob/master/docs/Introduction.md

Your app.js should look like this. You'll have to create App, Home and About views.

import React from 'react';
import ReactDOM from 'react-dom';

import App from './views/App';
import Home from './views/Home';
import About from './views/About';

import { Router, Route, IndexRoute, browserHistory } from 'react-router';

ReactDOM.render(
  <Router history={ browserHistory }>
    <Route path='/' component={ App }>
      <IndexRoute component={ Home } />
      <Route path='about' component={ About } />
    </Route>
  </Router>,
  document.getElementById('app')
);

And your views/App/index.jsx should like this. It is just a simple wrapper around your views. It has simple menu so we can test routing. chilren prop is every child component that is passed to it. In our case, children will be route that is matched in our app.js.

import React, { Component } from 'react';
import { Link } from 'react-router';

export default class App extends Component {
  render() {
    const { children } = this.props;

    return (
      <div className='App'>
        <Link to='/'>Home</Link>
        <Link to='/about'>About</Link>

        { children }
      </div>
    );
  }
}

Redux

  • Note that this part will be updated soon with more details.

Install redux

npm i redux react-redux --save

To learn what redux is, the best place to start are lessons taught by the redux's creator himself, Dan Abramov.

https://egghead.io/series/getting-started-with-redux

And to add it to your React app, for now follow official documentation (more info coming soon);

http://redux.js.org/docs/basics/UsageWithReact.html

Nice to have

This is everything you need, following the best practices for react and javascipt development in general. Beside that there is couple of more things, that I usually add to my projects. Check it yourself, and add it if you thing these are useful to you.

Absolute path resolving

To be able to include your files with absolute paths you need to set root path. (Example: components/views/App instead of ../../views/App.) In your webpack config, under resolve add root param. Note that you have add path at the top of the file.

const path = require('path');

...
resolve: {
  extensions: ['', '.js', '.jsx', '.json'],
  root: path.resolve(__dirname, './app/js'),
},
...

Using this will probably still give you linting errors in the editor, so we need to add Install eslint import resolver package

npm install --save-dev eslint-import-resolver-webpack

And add settings to your .eslintrc

...
settings: {
  "import/resolver": "webpack"
}
...

Update

As promised in comments, here you can find complete webpack.config.js and package.json.

To make it even easier, I created a git repository to go along with this blog post. You can find it on GitHub.

webpack.config.js

const path = require('path');

module.exports = {
  context: __dirname + "/app",

  entry: {
    javascript: "./js/app.js",
    html: "./index.html",
  },

  output: {
    filename: "app.js",
    path: __dirname + "/dist",
  },

  resolve: {
    extensions: ['', '.js', '.jsx', '.json'],
    root: path.resolve(__dirname, './app/js'),
  },

  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loaders: ["react-hot", "babel-loader"],
      },
      {
        test: /\.html$/,
        loader: "file?name=[name].[ext]",
      },
    ],
  },
}

package.json

This is minimal version of the file. Yours will probably have more stuff like author, license...

{
  "scripts": {
    "dev": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --hot --inline --history-api-fallback"
  },
  "devDependencies": {
    "babel-core": "^6.21.0",
    "babel-loader": "^6.2.10",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "eslint": "^3.13.1",
    "eslint-config-airbnb": "^14.0.0",
    "eslint-plugin-import": "^2.2.0",
    "eslint-plugin-jsx-a11y": "^3.0.2",
    "eslint-plugin-react": "^6.9.0",
    "file-loader": "^0.9.0",
    "react-hot-loader": "^1.3.1",
    "react-router": "^3.0.2",
    "webpack": "^1.14.0",
    "webpack-dev-server": "^1.16.2"
  },
  "dependencies": {
    "react": "^15.4.2",
    "react-dom": "^15.4.2"
  }
}

Comments (23)

Jack
07. Jan 2017, 19:25

This was mostly helpful and almost clear.

The use of add

...
{ something to add }
... 

made it hard to figure out where I should be adding things. Spent more time troubleshooting your tutorial than I would have wanted, which is a same because other than that small thing, it was a great tutorial.

Stanko
07. Jan 2017, 20:19

Thanks Jack!

You are right, it is a bit hard to follow, would it help to add complete webpack.config.js at the end? As it seems to me that repeating it every time there is a change, would make it even harder to follow.

Darrell
09. Jan 2017, 10:47

Hey Stanko!

Thanks for an excellent tutorial. Any chance you could add the complete config file at the end of your post as you suggested? And perhaps the package.json file as well? And does the .babelrc file go in the root directory? A lot of questions :)

Stanko
10. Jan 2017, 15:34

Hey Darrel, thanks for the feedback.

Yes, .babelrc file goes to the project root folder (I updated the post as well).

Now at the end of the post you can find complete webpack.config.js and package.json files. Besides that, I created a repository with working example to go along with the tutorial.

Hope you'll find it useful.

Pedro
27. Jan 2017, 03:29

Just an addition to Jack's comment, whenever one needs to show a patch in a file, I find that including one line before and one after the relevant lines makes everything clearer. I've seen articles where the author would go as far as to post the actual diffs from commits. I did manage to follow through the edits with no problem though.

That said, nice tutorial, although with a bit too much of loose ends, relying a lot on external guides. I also noticed react-router is missing from your final package.json, shouldn't it be there?

Anyway, looking forward to see the next updates, thanks!

Dev
31. Jan 2017, 15:17

Hello,

Thanks for this great article, just had some questions since I'm having difficulty running your code. See I've upgraded to webpack 2.x and did the necessary changes to make it run and most of it works well but I'm stuck at this error.

[error] ERROR in chunk html [entry] app.js Conflict: Multiple assets emit to the same filename app.js [/error]

I'm not sure what could be the problem causing it :/ Anyway it was a awesome read especially for me since I've just started on the whole webpack babel react thing.

Do you think you can update your code to run on webpack 2.x.x or thats too much to ask ?

Anyway thanks again.

Stanko
31. Jan 2017, 16:16

Hey Pedro,

Thanks for the feedback, it is really hard to get this thing up to date. I will try my best, and definitely have that in mind for the new posts. And you are right, I forgot to add react-router to the package.json. It is now updated, and I added a simple routing example (the one from this post) as well.

Hey Dev,

Thanks, unfortunately I don't plansto update this guide to Webpack 2, but I have a complete boilerplate for it. Check this post, it has much more options.

As for your error take a look at this thread. It seems to be a issue with CommonsChunkPlugin.

Cheers both of you!

leila
21. Feb 2017, 15:39

hmm sorry, complete noob. why do we need file loader? doesnt webpack also build the .html file into the dist folder? thanks!

Stanko
21. Feb 2017, 16:11

Hi leila,

file-loader is used to process html entry point. If we haven't added it, webpack wouldn't know how to handle html files.

In a same way we added babel loader to handle .js and .jsx files.

leila
21. Feb 2017, 18:36

ahh i see gotcha! thanks for this helpful article!

Elvan
24. Feb 2017, 16:46

Hi Stanko,

When run npm run dev I get an exception: ERROR in ./app/js/app.js

 Module not found: Error: Cannot resolve 'file' or 'directory' ./../../node_modules/webpack/buildin/module.js in C:\react_tutorial\react-tutorial-master/app\js
 @ ./app/js/app.js 1:0-57
webpack: Failed to compile. 

Please help

Stanko
01. Mar 2017, 08:30

Elvan,

Can't really tell what is wrong from the stack you pasted. It seems that you are probably missing node modules.

Try running webpack --display-error-details, it may give you more details about the error.

In the end, clone this repository, I created for this tutorial, and compare it to your code.

Payal
07. Mar 2017, 05:38

Wonderful article. Thanks for sharing Stanko.

Tom
10. Mar 2017, 10:55

Very useful guide, thank you.

Attila
17. Mar 2017, 13:36

Hi Could you update to webpack 2.x with other modules newest versions?

Stanko
17. Mar 2017, 15:18

For webpack 2, check this post.

Thanks everyone for the kind words.

Alexandre
29. Mar 2017, 21:29

Is your package.json correct? I don't see "eslint-import-resolver-webpack". Thanks for the post.

Stanko
31. Mar 2017, 12:26

Hi Alexandre,

I'm on a vacation, with a very bad internet.

It should be correct, as path resolver is "Nice to have" I didn't include it in the demo repo. Feel free to add it yourself and open a PR.

Cheers and thanks!

Ram Kinkar
04. Apr 2017, 11:14

thanks for this helpful article!

Stuart
22. Jul 2017, 21:53

Thanks for the article.

However, when I try to run

node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js

I get the following error:

Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.resolve.extensions[0] should not be empty.

not sure how to resolve.

Stanko
24. Jul 2017, 18:16

@Stuart I'm pretty sure that is happening because you are using webpack 2 or 3, and this tutorial was written for webpack one. You can install webpack 1, but I wouldn't recommend downgrading.

I already written boilerplate for webpack 2 (which should be soon updated to use webpack 3). So you can use that or Facebook's offical create-react-app.

I won't be updating this tutorial, but I might write another one in the future.

Cheers!

@Ram you are welcome mate!

Tony Bai
12. Sep 2017, 08:31
 resolve: {
  extensions: ['', '.js', '.jsx', '.json']
}, 

this part of code will caused an issue when you configure scripts in package.json like

 "scripts": {
  "start": "webpack-dev-server --hot"
}. 

When I try to run npm start command, Invalid configuration object issue returned due to the configuration.resolve.extensions[0] should not be empty.

So I change the 0 index of extensions to ' ' solve this problem. And also thanks for your blog to help me understanding how react, webpack and babel are working.

Stanko
12. Sep 2017, 10:51

Hello @Tony Bai,

I mentioned that already above, that is happening because you are using webpack 2 or 3, and this tutorial was written for webpack one.

Glad it helped, cheers!