I think you are missing esbuild. E.g. Elixir creator José Valim recently announced that the Phoenix Framework is switching away from Webpack/Node.js to use esbuild, a single binary which they download automatically for new users to run the asset build pipeline. Hongbo has actually also mentioned it elsewhere in this forum, as an option for ReScript.
webpack and babel don’t require a master, or constant support, if they are used simply.
2ish years ago I replaced my team’s awful grunt setup with npm scripts. It took less than 2 weeks, mostly spent wondering what various grunt plugins were doing (or not) in our previous setup. From scratch, it might have been 4 days altogether. Since then, we haven’t had to touch anything, except to update dependencies. If we ever start a new project, we could use everything basically as-is.
The key difference is the old system had tons of plugins, bridges, and interdependencies, each with unique config, releases, and docs (usually poor, often non-existent). That kind of set-up does require a master, and constant vigilance. Our current system uses webpack and babel, but webpackonly handles js/ts compilation and bundling. Utilizing build tools independently means we almost exclusively use core behavior, with minimal configs and fantastic docs, and we can update or replace a tool at will with zero collateral damage.
For anyone who’s curious:
tsc (~ 25 line config file) - type-checking only
webpack (~25 lines of basic config) - babel-loader is the only loader, zero or one plugin(s). Our config also has ~90 lines of app-specific bundle-splitting (including our only plugin), but that’s due to how we serve the code, which is a special case.
babel (~12 line config file) - compilation of all code, using env, react, and typescript presets with no other plugins.
eslint (~25 lines of basic config) - Handful of plugins as you wish. (uses babel for parsing) Our config is much bigger because we hardcode a ton of code style rules, but the default presets are pretty good.
jest (~40 line config file) - uses babel under the hood, but it includes babel-jest automatically
less (0 lines of config) - Not necessarily recommending, but it works fine. Use whatever your favorite CSS tool is.
node (maybe ~50 lines of JS total?) - a few short JS scripts for things like cleaning your output folder and moving static resources. We use a couple low-level file management packages.
Then just call them as needed in trivial npm scripts. We use a couple simple packages to run scripts in parallel, but you might not need them. (e.g. npm run dev simultaneously calls npm run type-check, npm run build:code, npm run build:css, etc.)
If you don’t use TypeScript, this setup is even simpler; I imagine Rescript would add about the same amount of plugins and settings as TS does. (I haven’t had a chance to give Rescript a proper go yet.)
So far, with a few months of production experience, both around a larger existing app and implementing a new static site project, I found that I would spend weeks without thinking about the build, and then a day or two hunting around dealing with something at the intersection of two build tools - nextjs and next-transpile-modules, babel and jest, nextjs and babel - as I introduced new libraries or upgraded an existing library or tool. The one to two days would be randomly sifting through github issues, looking to see if someone else had already discovered a workaround, because I hadn’t mastered webpack or babel yet. Maybe one to two days every few weeks is not that much time overall.
Yeah, in my experience (if you can’t tell ) the issues almost exclusively come from how tools interface with each other. One thing we couldn’t escape is that babel is used (separately) by webpack, jest, and eslint. Luckily, these are some of the most well-maintained inter-package connections, so we haven’t had any issues.
I haven’t tried out nextjs, but I imagine it would make it harder to segregate your tooling.
Thanks! I was pretty proud. It also took us from ~1min for every build to ~8sec each (after the first cold-build to get caches set up). I blame that difference on how bad the original system had gotten though. It was highly inefficient, running everything serially, some things twice, and without caching anything. The problem was we couldn’t fix it without scrapping it, because we couldn’t figure out what it was doing.
Unfortunately I can’t share my work setup, but I’m starting to construct a personal site (been an engineer for 4 years and I still don’t have one ), which will have very similar tooling. (Hoping to test out Rescript, maybe GraphQL, etc) I’ll come back and add a GitHub link once I have it put together. (Sometime in the next week or two if I get a chance.)
I hadn’t heard of neutrino, it looks interesting. I think it would have the same issues as CreateReactApp/etc though, which is that it’s doing a lot under the hood. That’s great if you want to setup a project quickly, but I think it would inevitably lead to the debugging and grok-ability issues my setup is designed to avoid, especially if you ever need custom or edge-case behavior.
Neutrino seems targeted at someone who starts or runs a bunch of different projects, and thus takes full advantage and will “increasingly master” it’s functionality. My setup is ideal for almost the opposite: a single long-lived project supported by a devoted full-time team. Low-level discrete tooling is particularly beneficial for us because it:
prevents dependency maintenance issues (version conflicts, dead packages, outdated or incomplete docs, deep or incompatible dependency trees), and
allows anyone to quickly and confidently fix issues or make changes, with minimal knowledge and easy research of the related tool, and little or zero knowledge of our other tooling. (I also commented a link to the relevant docs next to almost every setting in our configs.)
(Sorry for all the long comments folks, just hoping to help prevent headaches)
I have been following your approach and I got as far as jest unit tests. I haven’t attempted rescript-react-testing-library with jest yet. I like it so far.
After struggling a bit and eventually succeeding to get jest to work with esm, I realized that part of the problem that I was trying to solve was ensuring the same JS code works in the browser as well as node. That is one of the core problems that NextJS solves (although right now NextJS is a little behind in enabling ESM). I’ll reconsider nextjs later on, given this alignment with my needs.
Update: I ended up switching back to commonjs and using nextjs with the suggested configuration for a csr spa, making use of the “with-jest” example. I will resume using esm with webpack or vite directly in my next greenfield project. One interesting observation is that webpack and webpack-dev-server have >600 transitive dependencies, whereas nextjs has about 300 transitive dependencies.
Did you consider forgoing babel-loader and using babel-cli in watch mode? If one used babel-cli, then it might be simplest to introduce a new file extension for js files before the babel transform (e.g. “cmp.res” → “cmp.modernjs” → “cmp.js”).
I am guessing you chose to use babel-loader because it is a very commonly used plugin.
Very interesting. I still haven’t had a chance to try nextjs. This doesn’t bother me too much, because the important thing to me is not needing to deal with dependency collisions, and webpack/nextjs would need to handle any issues in their dependencies. It would be nice if you could have no sub-dependencies, but I don’t think that’s feasible in the JS/npm world.
I didn’t really consider that, although it would definitely be in the right spirit. To me, babel-loader is the one of the least problematic connector dependencies out there, because it’s incredibly widely used and well-documented.
As a side-note, although I did build watcher scripts when I setup our system, none of us use them, because our builds are so fast it’s easy to just run the “full build” command a terminal.
The fact that you need a rewrite, _app.js boilerplate, a catchall page, and some integration with a real router to implement a CSR SPA with next.js feels like slightly too much baggage. Razzle might be a better choice, if one is simple looking for a more flexible create-react-app.