Jump to content

Setting up Webpack, Babel and React from scratch

Posted in React · 10 minutes read

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)

12. September 2017
Stanko

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!

12. September 2017
Tony Bai

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.

24. July 2017
Stanko

@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!

22. July 2017
Stuart

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.

4. April 2017
Ram Kinkar

thanks for this helpful article!

31. March 2017
Stanko

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!

29. March 2017
Alexandre

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

17. March 2017
Stanko

For webpack 2, check this post.

Thanks everyone for the kind words.

17. March 2017
Attila

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

10. March 2017
Tom

Very useful guide, thank you.

7. March 2017
Payal

Wonderful article. Thanks for sharing Stanko.

1. March 2017
Stanko

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.

24. February 2017
Elvan

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

21. February 2017
leila

ahh i see gotcha! thanks for this helpful article!

21. February 2017
Stanko

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.

21. February 2017
leila

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

31. January 2017
Stanko

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!

31. January 2017
Dev

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.

27. January 2017
Pedro

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!

10. January 2017
Stanko

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.

9. January 2017
Darrell

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 :)

7. January 2017
Stanko

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.

7. January 2017
Jack

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.