Tips for local developing of React modules with babel and webpack

Local modules development would be much easier if changing module source code would immediately be visible in project that is using it, without needs for module recompiling or reinstalling.

Preferred way of developing modules is to write modules in ES2015 and compile it to standard javascript before publishing on npmjs.

One of the obstacles that makes local development harder is a problem with npm linking and the other one is that local module including would include compiled code instead of ES2015 source code.

Here are two tips to workaround around these obstacles.

Tip 1 — npm link (even if it does not work as expected with peerDependencies)

Let’s say that we are developing module my-awesome-module that is consumed in my-project.

my-awesome-module is a component for React, so it lists React as peer dependency.

{
"version": "1.0.0",
"scripts": {
"build": "babel src --out-dir lib",
}
"peerDependencies": {
"react": "^0.14.3",
...
}
...
}

To consume my-awesome-module when developing local application we would list it in dependencies section of package.json (or in devDependencies if you want) of my-project package.json, ie:

"dependencies": {
"my-awesome-module": "^1.0.0",
...
}
...

To avoid publishing new version of my-awesome-module for each change, we can also install it from a path:

npm install /full/path/to/my-awesome-module

Even faster — we can symlink a package, so we are not required to install my-awesome-module every time we change it. To do this, we use npm link:

cd /full/path/to/my-awesome-module
npm link
cd /full/path/to/my-project
npm link my-awesome-module

Sadly, this does not work as expected in cases when it is important not to duplicate dependencies (React is such example).

More info for this behaviour can be found here and here.

Solution is to tell webpack to prefer project local node_modules with resolve.root configuration option.

Here is relevant part of my-project webpack.config.js configuration:

var path = require('path');
module.exports = {
...
resolve: {
root: path.resolve(__dirname, 'node_modules'),
}
...
}

Now every change made in my-awesome-module would be reflected in my-project.

Tip 2 — Consume ES2015 sources instead of compiled module

We still have one issue thought — my-module code referenced in my-project is compiled code, not ES2015. This means that we still need to compile my-awesome-module when we change source code.

Webpack resolve.alias to the rescue. resolve.alias configuration option allows replacing modules with other modules or paths.

We have to tell webpack to load ES2015 source code when we require my-awesome-module instead of what is written in it’s main package.json.

Here is updated relevant part of webpack.config.js for my-project:

var path = require('path');
var fs = require('fs');
// 1. get absolute path for symlinked module
var myAwesomeModulePath = fs.realpathSync(
path.resolve(__dirname, 'node_modules/my-awesome-module/src')
);
module.exports = {
...
resolve: {
root: path.resolve(__dirname, 'node_modules'),
alias: {
// 2. setup alias
'my-awesome-module': myAwesomeModulePath,
}
},
module: {
loaders: [{
test: /\.js$/,
loaders: ['babel'],
include: [
path.join(__dirname, 'src'),
// 3. use babel loader for my-awesome-module
myAwesomeModulePath,
]
}]
}
...
}

Now, every change in my-awesome-module will be visible in my-project without reinstalling or recompiling required module.

Example for this approach is visible in example app for pouchdb-redux-helper module. And here is the relevant commit.

Like what you read? Give Bojan Mihelac a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.