I've been using Jekyll/Ruby since 2014 for my website, with a few custom plugins I wrote myself. And I've never really needed to do anything like this. It "just works".
My Go and C programs are the same: "just works". I have some that are close to a decade old.
I don't love how bundler works by the way; I think it should/could be a lot better in many different ways. Same for Jekyll. But once it works, it generally keeps working.
I don't think Jekyll (or Ruby) are a paragon of stability. I'm sure some stuff has broken over the years. It just seems to break a lot less than the JS/Node ecosystems.
There are countless gems, libraries or packages out there that make your life easier and development so much faster.
But software (in my experience) always lives longer than you expect it to, so you need to be sure that your dependencies will be maintained for that lifetime (or have enough time to do the maintenance or plug in the replacements yourself).
(Wish I was joking, but sadly I'm serious.)
I disagree. This is an easy problem to avoid with minimal due diligence, people just choose convenience and make unnecessary tradeoffs.
* Use the standard library (ironically not available for Node projects). It will be built with better backwards compatibility almost every time. What deprecations do occur will likely be VERY WELL documented with much quicker adaptions.
* Limit third party dependencies. Do you really need an ORM for your apps 40 sql queries? How long would it take you to scaffold it with GenerativeAI then make it production-worthy without the ORM? 1 hour? 5 hours? 20 hours?
* Pick technologies with better track records. Maybe don't use Beta software like Swift Data for your iOS App. Maybe choose golang for your API even though it'll take a little bit longer to build it.
There's a balance to be struck between LeftPad scenarios and "Now there are 37 competing libraries".
I'll acknowledge here that there seems to be a significant difference between Python projects and Node projects: in my experience, a small Python project has a handful of dependencies and maybe a dozen sub-dependencies, while a small Node project usually has a handful of dependencies and a few hundred sub-dependencies. That's where Python's "batteries included" motto does seem to help.
I think we're actually in agreement. My assertion is that for projects which want to avoid constant maintenance, particularly small projects, you can make architectural decisions some of which could significantly improve the maintenance outcome. Of course there are trade-offs to those, and if you make the wrong architectural decisions it can cause more harm than good.
Maybe I'm glib for calling it "easy" but for many leftpad scenarios it really is a "holy crap why did you think that was ok" scenario in my experience. Lets avoid those scenarios when we can.
Having a machine do codegen to map your queries to objects is still an ORM, except now it's nondeterministic and not updateable.
(mind you, I come from C# where you simply use LINQ+EF without worry, or occasionally Dapper for smaller cases)
???
Node.js has an extensive stdlib [1].
Contrast that with Python which practically requires a 3rd party HTTP client.
Or Java that doesn't even have JSON support.
It's absolutely because there's something wrong with Python, the package management, and also the type safety. JVM languages haven't had these problems for 20+ years.
The same problem in Python is much easier now because you can ask the uv resolver to limit itself to some earlier point in time.
You can do `uv pip install --editable . --exclude-newer=2022-01-01` and you will end up with a resolution from two years ago. Since uv can also install older python versions automatically you can easily bisect you to a newer point.
The author of the blog post is trying to run a static site generator. A static site generator doesn't need to be able to do anything that Node provides that can't be done with the World Wide Wruntime (which they're already going to use to verify the correctness of the SSG output). So use that runtime and tools that target it, not Node.
Fact: for a great many programs, NodeJS isn't even necessary to provide the operations that a given program needs in order to do its work, because there exist standardized, vendor-neutral, cross-platform, stable APIs, with broad sort by such companies as Apple, Google, Microsoft, Mozilla, etc.
Fact: the runtimes that support these APIs are already pre-installed on virtually every desktop and laptop computer.
Fact: you could write a static site generator targeting these APIs today and get a working program today and that will continue to work indefinitely, due to these vendors' commitment to backwards compatibility.
I just got burned by an old js (vue 2) project. I ended up re writing it using good old ssr in Django with htmx and alpine when necessary. Now it’ll run until the end of time. It doesn’t even have a build step.
It seems luck of the draw. My old React projects (old as in 2018) still work great with class components. I guess the Vue guy did say he would be more revolutionary, when he launched it.
Does it, though? Node wasn't exactly new 4 years ago, and plenty of other languages would offer a better experience for even older code -- Java, C, C++ to name a few.
1.5 hours to get running again?
1?
In exchange for needing to run C? How many hours would it take to build a Node app equivalent in C, I wonder.
50% of Java developers are still regularly working in Java 8 [0], which is the same solution that the author could have arrived at immediately—when starting up an old project after 4 years, use the same version you ran last time, don't try to update before you even have the thing running.
> C, C++
Not my experience, but maybe it depends on your operating system and distro? In my experience sorting through the C libs and versions that you need to install on your system to build a new project can easily take a few hours.
and dare i say this is the lucky case. i had problems reactivating some older project because some dependencies were not version locked and newer versions of those were incompatible with other versioned ones.
Any C/C++ project with even mild complexity has a good chance of being extremely difficult to build due to either missing libraries that have to be installed manually, system incompatibilities, or compiler issues.
Python has like 28 competing package managers and install options, half of which are deprecated or incompatible. I can't even run `pip install` at all anymore on Debian.
Even Rust, which is usually great and has modern packaging and built-in dependency management, often has issues building old projects due to breaking changes to the compiler.
All this is to try to say that I don't think this is some problem unique to JS at all - but rather a side effect of complex interconnected systems that change often.
A big reason Docker and containers in general became so popular was because it makes this problem a lot less difficult by bundling much of the environment into the container, and Docker is very much agnostic to the language and ecosystem running inside it.
Yeah, make sure no-one can ever fix your security vulnerabilities.
> As for compiler issues, just run it with the same compiler.
And when the same compiler doesn't exist for your new machine?
Freezing everything makes things easier in the short term, but much harder in the long term.
One can argue that this decision should be revised by debian but you should not install packages on system python installation for working into projects. Always use virtual environment.
» pip3 install supervisor error: externally-managed-environment
× This environment is externally managed ╰─> To install Python packages system-wide, try apt install python3-xyz, where xyz is the package you are trying to install.
As far as I can understand, they did this on purpose to dissuade users from installing packages globally to avoid conflicts with other Python environments.
Anyway, I'm not trying to argue about if that decision is right or not - I just wanted to use it as an example for my case that the JS ecosystem isn't alone and may even be far from the worst when it comes to this kind of issue.
Python is different here because in many linux distributions, there are many tools that rely on you system python. Python unlike node is not limited (in practice) to web applications. that's why you have to be more careful. So while I understand you are using this as an example, I don't feel that your comparison is apple to an apple.
They may or may not be running Node.js specifically, but I believe that many Linux distributions, as well as Windows, include JavaScript code in core applications. I don't see this as particularly different, except that they might choose to assume a single standard system Python that is able to be modified by standard Python development, whereas I would rarely expect that to be the case with however each component chooses to execute JavaScript.
No, they're not. I'm talking about core apps and services that are essential to a functional operating system. This is exactly the same situation. The difference is choices made by the OS and language ecosystem about how to manage dependencies in various use-cases. It is an apples to oranges comparison because of those decisions and not because of the language.
It's better all round to just assume that unless you're building something to be a part of the system itself, that the system interpreters just aren't for you. There's a special case for shells where they're actually UI, but I've seen so much effort wasted over the years trying to let system interpreters do double-duty as both system tools and development environments that I've come to the conclusion that it's simply not worth the hassle.
1. npm install
2. node my_script.js
First one downloads and installs all dependencies listed in a package.json file into a node_modules local subdir. This is equivalent to creating a pip venv, activating the venv, and running pip install against a requirements.txt file.Second one runs the interpreter with the script file and against the locally downloaded dependencies.
I.e. the local env dirs in Node.js are not a suggestion that you need to learn about, make choices, and actually use; they are just how the tool works by default, which makes the experience of using Node.js with NPM by far much better and less confusing than the default experience of using Python with PIP.
There are a load of scripts with `#!/usr/bin/python` (or similar) in `/bin`, which means package resolution can't look first in a local subdir otherwise all bets are off. Again: your system will break depending on which directory you run bits of it from. The system-provided process would be loading dependencies that it was not tested against. In case this is unclear, you do not want that to happen. It would be bad. On my system I can see python scripts involved in driver management. I do not want them to do unexpected things. It would be bad.
Python package management is a mess in lots of ways, but this particular choice isn't one of them.
Sure it can, it's just a matter of examining where it's being run from.
I wrote python-wool to load packages from a venv if it finds one.
I love Python but it has a terrible package ecosystem with mediocre tooling that has only gotten worse with time.
JavaScript has gotten better but it seems they are just re-learning things that were long figured out.
When I see new package managers, I just see a list of problems that they forgot to account for. Which I find strange when there have been many package managers that you can learn from. Why are you re-inventing the wheel?
We just had to workaround breaking changes in a patch version update of Spring Boot. Maybe it was true in 2005, but certainly not the case today. I know of products that are stuck in Java 1.8 and not because they are too lazy to upgrade.
However, some other projects around here using different application frameworks are stuck since the frameworks aren’t maintained or upgraded in ways that aren’t compatible anymore.
Looking into old Java code, it is hard to remember a time before enums and what a pain that is to deal with int constants instead of typed constants.
Node does not promise indefinite backwards compatibility, which is a design choice that they've made that allows them to shed old baggage, the same way that the Java developers chose to shed baggage in 8->9. Neither choice is inherently better, but you do have to understand which choice a language's designers were making during the time window in question when you go to run it later.
That's not the only reason. :)
Horrible syntax full of inconsistencies, bolted on type system with TypeScript helps but will always be bolted on, quirks everywhere, as if `null` was not bad enough they also have `undefined`, I can go on.
I simply avoid it for anything but small enhancements scripts on otherwise static HTML pages.
Likewise.
> I know [...] C, C++, C#, Python, Go, various flavors of Assembly
That's good. But these are all languages that either lack strong typing and or are themselves rather quirky.
Only C# and Go stand out, IMHO, as languages that are recently designed. Even Python did not have user defined classes in the first versions, and some things thus feel off (__len__, __init__, etc.).
Also C# and Go still have implicit nulls all over the place. Their designs show ignorance for modern language design. Sum-types, explicit null, immutability, sound type systems -- all lacking in all langs you mention.
So what languages do have these IMHO "Game changers"? OCaml/ReScript/ReasonML, Haskell, Elm, Rust, Gleam, F#, Scala, Kotlin, ...
Those languages really showed _me_ something important: how it could be better.
There is another group of languages that also sits on a unique place in the solution space: the LISPs (incl. Racket, Schemes and Clojure). I found it very worth while to learn to program with them as well.
If you do use C#, you may also want to add <WarningsAsErrors>nullable</WarningsAsErrors> to .csproj too.
The entirety of standard library is annotated since long time ago. All new and not so new projects are also null-aware. Pretty much either completely legacy libraries or libraries that explicitly removed Nullable: enable that is set by default for all new project templates do not have those.
As I mentioned previously - it isn't perfect, but the level of "good enough" of NRTs in .NET is such that the nullability is a solved problem.
And there's a reason practically nobody uses the languages you mentioned, and Javascript is so wildly popular. Most people don't really like or need type nagging systems. Sure, if you're trying to launch a rocket or doing something like building medical equipment or something else that requires covering your ass, then yeah, sure, go ahead and type the hell out of it. But for most programming tasks the languages you mentioned are overkill and frankly too obscure to use.
Also: TS popularity shows that not everyone in the JS community agree with you.
For server side web dev you will find that statically typed langs (Java, Kotlin, C#, Go) are a big chunk of the pie. Sure it also comes down to taste, but if you work in a large team, having stronger types can greatly help to keep the codebase in shape. (better IDE refactor tools, clearer for noob, harder to hide/ check in rubbish to git)
If you are afraid by obscurity, have a look at Kotlin!
TS is to the rescue when you have a big JS project, because that's what JS is not good at: big projects and large teams.
When I find my shell scripts from 20+ years ago, they still just run as intended.
OTOH with Node I always find myself in dependency hell with dealing with old projects.
For some definition of "without issues"...
You can open and build a back-end application that targets e.g. netcoreapp2.1 (6 years old target) just fine, it might require installing you an archived SDK for the build to succeed (which you can download regardless, it will complain that it is EOL though) but it's an otherwise simple procedure.
For library code it's even easier - if you have netstandard2.0 target, it will work anywhere from .NET Framework 4.6.1 to the latest version without requiring any maintenance effort whatsoever.
On Windows, Visual Studio will happily work with .NET Framework 3.0 (which is ancient) and more.
I know for sure that I can clone a random project on Github, hit `dotnet build` and usually expect it to work on the first try. The rate of bitrot for average unmaintained JS project cannot be compared. The average dependency graph of a .NET project is going to be 10 times smaller too.
If this isn't enough, there are tools like Yarn which encourage vendoring dependencies to ensure that as long as you have a copy of the repository, the yarn CLI, and a valid version of NodeJS, you can always run the project.
It was open much the same in both. If you're happy using outdated and unsupported components with security issues AND you can get hold of the right version of the dev tools and plugins AND your hiding environment still supports the old platform, you can maintain the old version with minimal change. But should any professional developer do this?
In domains where software is expected to run for years, if not decades, it's common to archive the entire toolset, along with libraries and SDK's in case you need to fix a bug/add a feature 10 years later. Obviously, in this case you can't have dependencies sitting somewhere on a server you don't control.
There are also situations where this is forced by regulation: you need to recreate build xxx.yyy.zzz 7 years after release because a customer reported a serious bug and it needs to be reported to the relevant regulatory agency and investigated.
Can remember trying to update a Unity project so it'd be still buildable for 64-bit Mac devices. The very first version bump I done resulted in several critical libraries no longer working and there being no clear alternatives to swap in.
It's arguable whether this is the correct decision, but it makes things slightly harder than they used to be
This depends highly on what dependencies are in your C# solution.
Same for his node project. If he'd stuck with dependencies that are just plain js - I'd bet money his project would have installed & built just fine.
By the time you're hitting the native addon apis and doing things like compiling python and C/C++ code... you're going to feel all the pains of those ecosystems too.
Pefectly acceptable? Perfectly? Really? I have 10 year old C and Go projects that build and run fine as if nothing has changed. I can upgrade the dependencies if I want to but that's on me. The projects themselves have no problem building and running fine.
How complex is the project? I know ML utilities like that can be as simple as 50-100 lines.
Locking env version is important.
Double points for using experimental / POC technology like gatsby or nextjs. They are expected to burn and fail
Try any old Spring project, where anything newer than JDK 8 will be incompatible. The only saving grace is that JDK 8 is still available, but even it will eventually reach EOL. And then you look at JDK 11 projects and realize that they won't run on anything newer due to Lombok issues, so that's another thing to update and fix.
I think the experience of code rot is universal and increases with the amount of dependencies you have.
This is true but there's also a factor from the language/framework in use. Node is especially bad because of it generates huge package dependency trees. Go is especially good because of the large stdlib (which I use to minimize deps in https://github.com/contribsys/faktory) and excellent backwards compatibility.
Come on… compare it with what java and python have.
Tell me a single thing that Oracle has added to the standard library. As far as I can tell, more and more of what was once standard is now getting offloaded and trademark-washed with the eclipse and apache foundations.
They do have syslog though https://pkg.go.dev/log/syslog
The company I work at has a ton of projects stuck on ancient spring versions and Java 8 (or Java 6 in one instance). They still insist on Spring despite being essentially unable to upgrade to a version newer than a decade old.
That's the reality of using the language in production projects.
Same as how you're likely to see more than just Python or Node being used in projects that list them in the tech stack, because there's native dependencies and tooling used.
My son found a disk with some of my old java project from college 20 years ago and that's about what it took to run them, first figuring out how to even download java and then making some minor changes to get them running. I think we gave up trying to get the actual applet based ones to run.
You also need a browser that has NPAPI. IE 11 was the most modern browser I am aware of that still supported applets.
The old GUI framework mentioned in the GP might have been Swing. It is still included in most JDKs and allows for cross platform desktop GUI application development with no other dependencies outside the JDK. Finding documentation on how to do GUIs in Swing is getting increasingly difficult though.
Haven't used javaFX for a while but this is worth a shot
There are a very small number of projects that specifically make it their goal to be backwards-compatible effectively indefinitely, and Maven is one of those. It's part of what people who hate it hate about it, but for others it's the main selling point.
And Maven is a package manager for Java. The main difference IMO? The usual way to do things in Maven is to always use exact versions for the dependencies. When I specify I want some dependency at version 1.2.3, Maven will use version 1.2.3 of that dependency even if 1.2.4 or later already exists.
That’s the usual way to do things in most teams working on app code I’ve been a part of (as opposed to library code where version ranges are preferable).
It will not build all old projects out of the box, of course. Specific versions of plugins may not be compatible with it or some dependencies may break on modern JDK. But chances of hitting this issue are much lower than in JS/NPM ecosystem.
`cmake ..` otoh, tends not to.
Most who are here saying that X, Y, or Z ecosystem "compiles and runs" fine after 4 years are talking about the time it takes to resume an old project in a language they're very familiar with running the same dependency versions, not the time it takes to version bump a project on a language that you don't know well without actually having it running first on the old version.
I can open my 4-year-old Node projects and run them just fine, but that's because I use the tools that the ecosystem provides for ensuring that I can do so (nvm, .nvmrc, engines field in package.json).
Npm at this point is probably the most used and most worked on package manager in history. If it is still one of the worst ones that is actually kind of interesting philosophically.
This whole thread just reeks of JS hate. I'm not perfectly happy with the language either, but nor am I perfectly happy with any other. If my employer wanted me to use C# or Go or whatever instead I'd be perfectly okay with that as well.
It's really not. I'm in the same situation with a Go + React project I haven't really touched in 3 years. The Go part is just `go build`, it still works after 3 years and I have a build in a fraction of a second. The React project doesn't build anymore at all. I used Parcel 2 at the time, and it turns out it's incompatible with a Mac M1 for some reason, and it's hard to update in my case. I also used Antdesign and some components (icons specifically) apparently disappeared. It should not be so hard.
This was with zero awareness of Svelte 5 (I only knew it was released — I wanted to upgrade because Node 14 is EOL now and I’d noticed some npm audit warnings.) So I feel I also learned something in the process.
> Two hours of my life gone...
Two hours of work after 4 years sounds ... perfectly acceptable?
And it would have run perfectly right away if the node version was specified, so a good learning, too
This feels like making a mountain out of a mole hill