Posted: October 21st, 2019
Topics: Webpack, React, Parcel, DevTools
I'm a big fan of using Parcel to bundle my website/web app assets. It goes by the "it just works" mantra, far removed from it's alternative, Webpack. That said, the "it just works" system doesn't give you a whole lot of options when it comes to customizing your builds & dev environment.
So, lets take a React app, for example. Create-React-App is probably the most popular solution for bootstraping a React build. You don't even have to see the Webpack configuration if you don't want to - it'll just work. One of the little pieces of magic in CRA is a package.json
value called proxy
. You can pass a localhost value to which you'd like to route all of your clientside API requests - typically to wherever you have your backend server running. This is done so that you don't have to write any environment-dependant logic into your API handler -- you can just write API requests with relative paths.
Now, I recently began a side project for which I've opted to go serverless. My app will be hosted on Netlify & utilize it's native lambda functions service so I can go monorepo for frontend/backend. Netlify has a great package called netlify-lambda
, which will build your functions locally and run them like you'd run a server normally. If I were using CRA/webpack, I'd just add my local lambda server address to the magic proxy
value & be done with it. But we're using Parcel, which doesn't allow for much customization of it's dev server, so a but more configuration is necessary.
Upon searching for answers, I found this Github issue where smarter people than me were asking this question. The creator of Parcel weighed in and posed the question: "why is this Parcel's problem?". I tend to agree, maybe CRA has spoiled us. In any case, he offered the solution of setting up your own mini Express server & using Parcel's middleware option along with http-proxy-middleware
. Pretty genius, here's what it looks like:
/// run-devServer.js
const proxy = require('http-proxy-middleware');
const Bundler = require('parcel-bundler');
const express = require('express');
const bundler = new Bundler('src/index.html', {
// Don't cache anything in development
cache: false,
});
const app = express();
const PORT = process.env.PORT || 3000;
// This route structure is specifc to Netlify functions, so
// if you're setting this up for a non-Netlify project, just use
// whatever values make sense to you. Probably something like /api/**
app.use(
'/.netlify/functions/',
proxy({
// Your local server
target: 'http://localhost:9000',
// Your production routes
pathRewrite: {
'/.netlify/functions/': '',
},
})
);
// Pass the Parcel bundler into Express as middleware
app.use(bundler.middleware());
// Run your Express server
app.listen(PORT);
So now, your package.json
may look something more like this:
"scripts": {
"rimraf": "rimraf dist/ && rimraf functions/ && rimraf .cache/ && rimraf node_modules/",
"clean": "yarn rimraf && yarn cache clean && yarn",
"dev": "concurrently \"yarn dev:client\" \"yarn dev:functions\"",
"dev:client": "rimraf dist/ && node scripts/run-devServer.js",
"dev:functions": "netlify-lambda serve api/lambda",
"build": "yarn build:client && yarn build:functions",
"build:client": "rimraf dist/ && parcel build src/index.html",
"build:functions": "netlify-lambda build api/lambda",
"serve:client": "parcel serve src/index.html",
"serve:functions": "netlify-lambda serve api/lambda",
"test": "jest",
"test:watch": "jest --watch"
}
Anyways, thought this was a great workaround for injecting some CRA magic into your own Parcel configuration. You can check this in action in my serverless React starter files.