4 ms·
> In recent years, a few mavericks and renegades have started to turn away from the world of JS frameworks and the inevitable bloated node_modules folders Some
by asciiresort 4y ago
> In recent years, a few mavericks and renegades have started to turn away from the world of JS frameworks and the inevitable bloated node_modules folders
Some projects claim they are "zero dependencies" but a few sentences into the readme and you see an `npm install` and now you notice there is `package.json` after all .
They meant zero runtime dependencies, which is still praiseworthy, but not zero `devDependencies`.
For my greenfield web app, I attempted to have zero dev dependencies on Node. I wrote some vanilla HTML, JS, CSS, and now needed to reduce network transfer. Requirements:
- minify
- inlining CSS file into HTML
- compress with Brotli
- print out final byte size
- nice to have: run a dev server that serves static Brotli
We know the story with WebPack. I looked to Parcel instead because it's the "zero-config" tool. It was promising because it inlined my `@import css` into my HTML, and logged out the filesize. The watch and reload feature wasn't working as promised, but that was nice to have anyways. For Brotli compression, which is mainstream at this point, I quickly needed to add a `.parcelrc` config. Their code sample didn't work because it turns out that in the real world you need to make your .parcelrc extend/inherit "@parcel/config-default". Parcel stopped logging at the compressed file sizes and not the final Brotli file sizes. Finally, Parcel's dev server is useless to me because you can't set encoding headers for "Brotli" files so the browser just attempts to download it. I didn't have a .gitignore file, and adding this one Node dependency on Parcel added 3,000 new files. To be fair, that's miniscule for a Node project.
I asked myself if I can achieve the same results with CLI tools that are not Node. I first looked to the Golang ecosystem. I stumbled upon https://github.com/tdewolff/minify https://github.com/tdewolff/minify. It just does minification. It's comparable in output size to other Node minifiers, but faster and just works. The lexer code is very readable, even for me who is not a compiler or systems expert. It's one command to install the CLI, and one command to Minify. You can do `npm install global -g` to get CLIs, but Node supply chains tend to be much deeper.
This bespoke one-liner does the CSS inlining for me:
RUN sed -i "s/@import \"style.css\"/$(cat style.css)/g" index.html`
For Brotli compression, I went with the official first party CLI ( also Go based ) instead of using a third-party wrapper like you'd have to do with JavaScript.
One thing I was missing in NPM scripts as a self-documenting way of describing what commands to build, run, test. I've seen cases where people are beholden to a `package.json`, sometimes when their project doesn't even involve JavaScript. When you have a hammer, everything looks like a nail. It turns out Makefiles are a better way of doing this, especially if you don't already have a active relationship with Node
.PHONY: help
help: ## This help.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
# DOCKER TASKS
build: ## Build the container
podman build -t ${APP_NAME} .
run: ## Run container on port configured in `config.env`
podman run -it --rm -d --env-file=./config.env -
p=$(HOST_PORT):80 --name="$(APP_NAME)"
It's even better than self-documentation, because the documentation is self-documenting:
$ make help
help This help.
build Build the container
run Run container on port
configured in `config.env`
- likortera 4y agoI don't find this anything better than just using Next.js or any similar framework. Custom solutions for bundling end up being custom reinvented square wheels in my experience.
- deleted 4y ago[deleted]
- asciiresort 4y agoThe point here is vendoring and dependency management. Makefile and sed are decades old, stable, available on every Linux distro including the Alpine docker image clocking in at 30mb total. The entire image containing an Linux distro with all the needed CLI tools is 30mb. └─ ▶ podman build -t base . && podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/base latest b593261330c1 1 minutes ago 30.5 MB The only dependency here is the Go based minifier, and Go programs are self-contained executables. Alpine contains an entire package manager. Compare with adding Node and one CLI for minification. └─ ▶ echo 'RUN apk add npm' >> Containerfile && echo 'RUN npm i -g terser' >> Containerfile && podman build -t node . && podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/node latest a28f38037137 6 seconds ago 93.5 MB localhost/base latest b593261330c1 5 minutes ago 30.5 MB Assuming dependency management was equal level of maintenance with Node, the spirit of Node ecosystem tends accept being whimsical with breaking changes. Terser or whatever Node-based processing library will introduce breaking changes to their config in relatively small timescales ( months, years, instead of decades ). That's not inherently a technical fault of the ecosystem as it is a cultural one. The popularity of JavaScript is its own undoing. Opting for tools in other web-friendly languages selects for a different type of developer. That's just for minification. Want to inline CSS? That sed command is 66 characters long. That's about the same as Parcel's zero-config config boilerplate that is NoOp: https://parceljs.org/features/plugins/ https://parceljs.org/features/plugins/. Surely a WebPack config is more than 66 bytes. I proceeded to add Parcel to compare how much it increases the image size, unfortunately it failed because there is a Python dependency: └─ ▶ echo 'RUN npm i parcel' >> Containerfile && podman build -t parcel . [2/2] STEP 18/18: RUN npm i parcel npm WARN deprecated stable@0.1.8: Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility npm ERR! code 1 npm ERR! path /usr/share/nginx/html/node_modules/@parcel/watcher npm ERR! command failed npm ERR! command sh -c node-gyp-build npm ERR! gyp info it worked if it ends with ok npm ERR! gyp info using node-gyp@9.0.0 npm ERR! gyp info using node@18.2.0 | linux | arm64 npm ERR! gyp ERR! find Python Fine, I added Python, Make, and other transitive dependencies to see how deep this rabbit hole goes: └─ ▶ podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/python latest 05582c8b2425 2 minutes ago 156 MB Will Parcel installation work now? Nope, here's the new error trace: https://gitlab.com/-/snippets/2364041 https://gitlab.com/-/snippets/2364041. It appears this is a dependency issue with Parcel, Maybe?, with this similar 6 week old thread: https://github.com/parcel-bundler/parcel/issues/8152 https://github.com/parcel-bundler/parcel/issues/8152, which reiterates the painpoint about Node. Let's take a step back and forget the Parcel example for a second. How about NextJS? From that 30mb Alpine base image: ```Containerfile RUN apk add npm && npm i next react react-dom └─ ▶ podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/nextjs latest d177ee032ff0 9 seconds ago 266 MB Kudos to NextJS for installing properly at least, but that single command added 266-30mb = 236MB to the image, and this is where the bloat creeps in. Thinking 200MB is a nothingburger is how we end up with gigabytes large images.