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:
|
|
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 install
s and npm uninstall
s. 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:
|
|
to
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:
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.
|
|
had to be changed to:
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:
with a more standard inclusion of our bundle. Refer to your config file to see where this file will be.
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
.
|
|
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.