From JSPM to Webpack for React

Why I chose JSPM

My favorite thing about JSPM was running through the documentation and everything just worked!

I chose it because of the way that it handled all the different ways of module dependency for you. This worked with modules from NPM or even direct from github.

Development was also easy. System.js took care of all the loading on every page. I didn’t need to build anything, setup gulp rules. It just worked. During deployment, you would issue a single command on your production server and it would package evertything up and, in some sort of wizardry, would insert it into your app for you. For someone who doesn’t have much patience for learning the intracacies of build systems and settings, this was perfect.

Why I’m leaving

Once bundled, my project is fast and responsive, but while in dev mode it’s unberably slow. Every single file is requeststed on a fresh page load. If you’re building a React app and modularizing as much as possible, this leads to hundreds of requests. This takes time. About 15 seconds. On every page load.

I dove into the mega threads on github. I upgraded to beta versions of JSPM, but nothing made much of a dent.

I would still consider JSPM for smaller projects and maybe this will all get resolved, but for now, here’s my path forward.

First thoughts

After reading up on webpack, it became clear that the flow is very different from JSPM. JSPM (or more accurately system.js) essentially (from my perspective) builds a bundle in the browser as it loads. With Webpack, we will need to build our bundle explicitly whenever we change code, and then link to that bundle on our webpage. To build the bundle, we need use webpack on the command line to do so. Fortunately, webpack has a watch mode that can quickly make bundles do to caching unchanged modules.

Initial setup

Our requirements are quite simple. I want to run a react app and make use of es6 features by using Babel.

The first step is to install webpack globally: npm install -g webpack.

…and the the dependencies we need: npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react json-loader

The last two are the presets that allow us to parse es6 and JSX files.

The second step is to create our config file. It should be called webpack.config.js and live in the root of your project. This is mine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
},
{
test: /\.json$/,
loader: 'json',
}
]
},
resolve: {
extensions: ['', '.js', '.jsx']
}
};

Config Gotchas

JSX and JS loading

There are a couple gotchas. In JSPM land, I was using the .jsx suffix for all my JSX files. JSX kept failing to be parsed. I would get Module build failed: SyntaxError .... unexpected token. Of course all the search results say to use the babel-preset-react. What I didn’t realize is that in the module.loaders config value, my regular expression was only for .js files. If we use /\.jsx?$/ then that will work for both .js and .jsx files.

JSON errors

Secondly, I ran into module parse errors loading .json files. For this we need that second object in the module.loaders collection that connects json files with the json-loader package.

Fix as you go

Now for the hard part. With a decent, albeit basic, webpack config file in place, we can run webpack at the root of our directory to get our build. You’ll probably get some errors. The only way forward is to fix them as they come up.

Fix as you go Gothchas

My main strategy was to try and get the webpack command compiling. Once that was working, I would use the webpack --watch command as I messed with code changes. But I actually had a difficult time getting the --watch option to work. As I was hacking all this out I was doing plenty of npm installs and npm uninstalls. At one point I uninstalled some dependencies. So if you get a Module not found error on a 3rd party module, try re-installing all the packages you know that you need (including webpack!)

Filenames

I ran into errors finding .jsx files. I would get a webpack error saying mymodule.jsx not found. If you refer back to the config file, the resolve entry has the .jsx extension so we are able to refer to the modules without explicitely including the .jsx extension.

So my first code change was to change my import statements to not use the .jsx extension. For example, I changed:

1
import MySubModule from './my-sub-module.jsx!';

to

1
import MySubModule from './my-sub-module';

I like this change a lot! I just did a find and replace in bulk.

Packages

I basically went through my config.js file used by JSPM to see what the package situation was. For all the dependencies that are in NPM, the fix is simple. Just npm install each of those packages. Webpack will find them from there.

Nodelibs

A lot of my non-NPM dependencies were node libs installed by JSPM via Github. For example, in the config, there is an entry (and a ton of similar ones) for:

1
2
3
"github:jspm/nodelibs-events@0.1.1": {
"events": "npm:events@1.0.2"
},

The solution was to replace these with an appropriate NPM module. In the case of the above, we replaces the JSPM version with the events package.

1
import events from 'nodelibs/events';

had to be changed to:

1
import events from 'events';

Of course, imports of other node libs would need similar code changes.

Script inclusion

None of this matters if we don’t include the code! In our HTML pages we need to replace the familiar system.js inclusion code:

1
2
3
4
5
<script src="/jspm_packages/system.js"></script>
<script src="/config.js"></script>
<script>
System.import('js/main');
</script>

with a more standard inclusion of our bundle. Refer to your config file to see where this file will be.

1
<script src="/build/bundle.js"></script>

Deployment

The deployment is quite similar. Running webpack from the command line or via a deployment script is all it takes to generate the same bundle file that you’ve been using in development. Heck, you could even include the built bundle in your repo and be done with it. Which if you read below… has a (uncomfortable) place.

Deployment Gothchas

My production server doesn’t have much memory. So moving all the packages to NPM caused some problems. The use of JSPM splits up the deployment workload because NPM runs and then JSPM runs later. However, with webpack NPM is the star of the show. I was having trouble getting my npm install --production to finish without crashing due to a lack of memory. My solution (not a good one) was to mark the dependencies for webpack (including all the babel stuff above) as dev dependencies by modifying the package.json file to move those package names under devDependencies.

1
2
3
4
5
6
"devDependencies": {
"babel": "^6.5.2",
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6

Dev dependencies are not installed when the production flag is used with npm install If we include our bundle.js file in the repository we have no need to do any webpacking on the production server, since it will already be bundled.

This strategy is generally frowned upon, but it makes the NPM install process shorter and less memory intensive. I didn’t go so far as to isolate packages I only use on the client as dev dependencies since I hope to upgrade my servers at some point and don’t want to go too far off the beaten path. I plan to revert this change some day.

Dev-ing

Now that we have webpack in place, we can use webpack --watch while we work and all our JS updates are bundled at every change. But don’t worry, it’s very fast since it caches unchanged modules in this mode.

Next Steps

Webpack has a development server built-in. I still am using gulp for sass and browser-sync which is why I haven’t used the development server. But I know sass and browser-sync are both options that can be added onto webpack. I will investigate that.

Webpack also provides this cool feature of multiple entry points. Here is a good description of that. I think moving forward I will try to take advantage of that feature.

avatar

Dev Blog