Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EFI: Express LTS Strategy #196

Open
wesleytodd opened this issue Feb 24, 2024 · 49 comments
Open

EFI: Express LTS Strategy #196

wesleytodd opened this issue Feb 24, 2024 · 49 comments
Assignees
Labels
top priority Issues which the TC deem our current highest priorities for the project

Comments

@wesleytodd
Copy link
Member

Many decisions on which tools to use, coding patterns to follow, and generally run some technical decisions depend on how we decide to treat Node.js Version support and how to run major version releases. There is one requirement for an LTS strategy for this project:

  1. We must maintain strict Node.js version support within a single express major

There are some really nice to haves:

  1. Keeping our version support modern so we don't block node core from making progress
  2. Keeping our version support modern so we don't get old outdated bug reports or security issues
  3. Keeping our version support modern so we can adopt newer patterns faster

And then there are a lot of things to discuss around the cost and priorities of these things. I think we need to have this discussion, and I was waiting on things getting sorted more generally, but to keep the conversations focused I am opening this issue now so folks can centralize the conversation someplace without rehasing it in many different dependent issues like #184.

@ljharb
Copy link
Contributor

ljharb commented Feb 24, 2024

Keeping our version support modern so we don't block node core from making progress

This merely requires continually testing on all supported versions of node, which both myself and pkgjs have actions to help you dynamically create a matrix for.

Keeping our version support modern so we don't get old outdated bug reports or security issues

I'm not sure what this means - as long as modern node is supported, a bug on an older supported version of node is still a bug.

Keeping our version support modern so we can adopt newer patterns faster

this really only applies to syntax, and that can be transpiled, so it needn't be a forcing function most of the time.


One of the inherent negative costs of any major bump is that people will be left behind on the old major, and backports for bug and security fixes become much more annoying and unlikely. When a major bump drops support for an engine, it makes upgrade the platform for that user much harder. I strongly suggest not arbitrarily adhering to someone else's support plan (LTS, eg) and instead, decide which node versions to support based on what minimal featureset is needed for express to support the user patterns it wants, and no fewer.

In the event that a dependency has a tighter contraint than express otherwise needs to impose, the tradeoffs should be ad-hoc weighed between letting that dependency make decisions for this project, vs the cost of finding an alternative dependency. (in most cases, i'll be happy to maintain an alternative dependency if that resolves this question)

@kibertoad
Copy link
Contributor

this really only applies to syntax, and that can be transpiled

Transpilation is not the best approach for a framework, as that can cause unpredictable runtime overhead in some cases, especially when transpiling to a much older version. And it complicates debugging significantly.

@kibertoad
Copy link
Contributor

@ljharb If the approach is to base supported version on desired libraries / syntax, it might make sense to start forming a wishlist for these. I'll start:

  1. Modern testing library. My personal recommendation would be vitest, which requires node 18+, which is roughly similar to native Node test runner
  2. Modern validation library. Typebox is Node 16+
  3. Async/await (Node 8+)
  4. Async iterators (Node 10+)
  5. Optional chaining and nullish coalescence (Node 14+).

That to me looks like 18 would be great, and Node 14 would be a bare minimum.
What are the alternatives if Node 18 is too high of a bar? Minimalistic stuff like tap?

@ljharb
Copy link
Contributor

ljharb commented Feb 25, 2024

You say “minimalistic” like it’s not one of the strongest arguments to consider a tool.

@kibertoad
Copy link
Contributor

@ljharb It's subjective, of course, but I would take DX over minimalism any day. Adjustable height table that you control by a handle mechanically is minimalistic, but pressing the button to adjust the height is so much faster and more convenient.

If I can get away with not doing something by hand in coding with no runtime drawbacks, I will. And experience of using TAP in IDEs is so poor, I sometimes want to scream when debugging failing tests for fastify libs. And uncomfortable tooling = less outside contributors.

What are the advantages of minimalistic tooling? It's not even about learning curve, because sometimes it's even steeper as a result of having less convenience features available.

@ljharb
Copy link
Contributor

ljharb commented Feb 25, 2024

Those were the same arguments that supported using jest, and the reasons most people are trying to move away from jest now are the same reasons I’d give.

@kibertoad
Copy link
Contributor

@ljharb No, problem with Jest wasn't that it had poor DX. Actually, its API was so good, that both vitest and bun pretty much copied it verbatim, and it still hold up well. Problems with jest was that it
a) monkey-patched a lot of stuff that it shouldn't have
b) was abandoned by Meta

@ljharb
Copy link
Contributor

ljharb commented Feb 25, 2024

Yes, I’m saying that the very good DX actually hid a lot of the complexity that turned out to be the root of the unavoidable problem. By avoiding that complexity, even at the cost of some DX, you save years of dev time later. My tape tests have never once needed any significant migration on 500 projects, over a decade ¯\_(ツ)_/¯

@kibertoad
Copy link
Contributor

kibertoad commented Feb 25, 2024

By avoiding that complexity, even at the cost of some DX, you save years of dev time later

Not necessarily. We've migrated our tests from Jest to Vitest without any changes, other than renaming jest. to vitest. and changing the config file. That's a one time migration effort, and then I'm saving time and energy every day.

It's somewhat strange to say that walking is superior to a car because you never need gasoline for it. That's technically true, but tradeoffs involved are way deeper than that.

@kibertoad
Copy link
Contributor

My tape tests have never once needed any significant migration on 500 projects, over a decade

YMMV on that. I definitely had to do more changes to node-tap tests when bumping major versions in fastify codebase than I had to do to migrate from jest to vitest, or when bumping vitest semver major. That's a benefit of a stable, mature and well-designed API - it doesn't need to change.

@sheplu
Copy link
Member

sheplu commented Feb 25, 2024

Changing a major version support (removing one) would be for me a breaking changed, and I agree to being tied to another project schedule release can be an issue.
But we should also work on lowering the need to support a large matrix of versions.

As posted there #172 it seems that a lot of other framework (and not all) adhere to something close to supporting the major version for the past 3 years which for us would make it between v14 and v16 if we were releasing a new major version today.

As this discussion is only for Express@5 (we won't change or deprecate versions supported by Express@4) we should discuss about that.
From what @kibertoad was also mentioning this would be quite close - and allow some "new" tooling

@ljharb
Copy link
Contributor

ljharb commented Feb 26, 2024

If there is concrete difficulty in supporting a particular version ("having to run CI on it" doesn't count), or a concrete feature needed (the "exports" field, native ESM for some reason, async/await syntax, etc), then it makes perfect sense to constrain the supported versions accordingly. "it's old" is not a sensible reason, nor is "people who aren't us don't support it", is all.

@wesleytodd wesleytodd added the top priority Issues which the TC deem our current highest priorities for the project label Feb 26, 2024
@wesleytodd
Copy link
Member Author

I think this conversation is a great example of where some agree upon technical priorities would help. I think my ideal is that we find a good happy medium. Yes that means we might drop node versions for sometimes "arbitrary" reasons but IMO we should value "predictable schedule" over "maximal version support" (phrasing might not be great, but the I hope that gets the point across). That doesn't mean we don't value version support, just that we want predictable timelines more than we want to support older versions.

@ljharb
Copy link
Contributor

ljharb commented Feb 26, 2024

Why is that beneficial?

@wesleytodd
Copy link
Member Author

Because it would help frame discussions which are tradeoff's against a common set of ideals. I think it is clear from the above discussion that there are good reasons and strong arguments for a few different approaches we could take. I would like it not to be left up to persistence of individuals in the discussion and then a TC decision if we can help it. I think a more healthy way to have that tradeoff discussion is to first get on the page for what our goals are.

@ljharb
Copy link
Contributor

ljharb commented Feb 26, 2024

Oh sure - i mean, what's the value in "predictable timelines"?

@kibertoad
Copy link
Contributor

@ljharb People who use the framework can plan their upgrades.

@ljharb
Copy link
Contributor

ljharb commented Feb 26, 2024

@kibertoad right, but no planning is needed unless there's a breaking change. what value would there be in forcing one?

@wesleytodd
Copy link
Member Author

Not only do we have planned breaking changes (hopefully low impact, but still breaking), I think everyone is better off if we release those breaking changes in a predictable way.

@kibertoad
Copy link
Contributor

kibertoad commented Feb 26, 2024

Maintaining consistent breadth of supported surface.
Any sufficiently complex library eventually reaches state where certain problems and behaviours only affect certain versions of Node.
Providing predictable pace of obsoletion is the most healthy approach for the ecosystem. It encourages keeping up with up-to-date versions, but also provides non-surprising cadence one can bring to their management for planning.
Encouraging users to update is healthy. Maintainers win, users win.
I wonder what inspired your "radical compatibility" philosophy. Are Node upgrades banned at your workplace?

@wesleytodd
Copy link
Member Author

I think a lot of the disagreements in these kinds of conversations are based on some "theory". Lets avoid too much theory crafting and keep the conversation grounded.

Also, to be clear about the value of having some guidance is to avoid the heat which seems to be entering this discussion. I know everyone here wants whats best for users and the project, but we should be careful not to get to deep into the "what inspired" type of comments. @kibertoad ideally we can keep to the topic and less on the motivations of the folks taking those positions.

@ljharb
Copy link
Contributor

ljharb commented Feb 26, 2024

@kibertoad i've had over a decade of workplaces where breaking changes in deps blocked node upgrades for months or years. The way to get people to upgrade node is to make it as easy as possible for them to do so, and having to upgrade multiple things at once increases risk.

@wesleytodd sure, well-planned and pre-announced breaking changes are the best way to do them! but if those breaking changes are avoidable, they should be; and once those breaking changes are made, what would be the benefit in planning for another set prior to the need existing?

@wesleytodd
Copy link
Member Author

wesleytodd commented Feb 26, 2024

what would be the benefit in planning for another set prior to the need existing?

Not saying we should do that. Again, this is theory crafting. If we go a whole year without landing a single breaking change, GREAT! If we don't, we have a documented support window where we can decide what to do. Maybe we get to that point and decide to skip the major and extend support for the old major. No one is going to complain about that lol. What they would complain about is a series of quick majors in an unpredictable way.

@wesleytodd
Copy link
Member Author

Maybe we need to call that out in the LTS strategy? This is a plan and we expect the plan to evolve to meet our needs. If we had technical priorities they might include (aka I think they should include) avoiding breaking changes.

@kibertoad
Copy link
Contributor

@wesleytodd Sorry if it sounded heated, I am genuinely trying to understand the rationale behind a particular line of reasoning. It is not my intention to attack it. @ljharb is a distinguished contributor to OSS space and I have utmost respect for him.

@ljharb Not sure if I follow. How is making library XYZ incompatible with Node 10 making upgrades to Node 12 any harder?

@ljharb
Copy link
Contributor

ljharb commented Feb 26, 2024

@kibertoad imagine v1 of a dep requires node 10, and v2 requires node 12. Often (altho thankfully less often in node than in other ecosystems), v1 won't work with 12. This means you have to upgrade both the dep, and node, at the same time, which increases risk. Certainly if you can upgrade all your deps either before, or after, a node upgrade, then it's fine - as long as all of the transitively used versions of that dep upgrade at the same time as you, which never happens.

@kibertoad
Copy link
Contributor

@ljharb Yeah, but as you've said, this is realistically very rare in Node ecosystem. If v6 works with Node 10-20, and v7 works with Node 12-20, I don't see a notable detriment to dependant team experience.

@wesleytodd
Copy link
Member Author

Sorry if it sounded heated

@kibertoad No worries! Thanks for the clarification, as I have only interacted with you on twitter I am still learning about you, sorry if I assumed something there.


I still stand by my statement that we are theory crafting here. We currently have a 10 year old major, that's not good. We also don't want to move right to running breaking changes every 6 months. My proposal is that we find a middleground which is:

  • Ergonomic for the maintainers (us)
  • Makes keeping updated as easy as possible for our users
  • Doesn't block breaking changes which are good for users for another 10 years

@kibertoad
Copy link
Contributor

@ljharb @wesleytodd @UlisesGascon Any proposals for the semvermajor calendar?

@ljharb
Copy link
Contributor

ljharb commented Feb 29, 2024

I think going from "10 years since v4" to a very fast pace is probably going to cause more harm than good, but probably good to set a date for v5, and then go for like, yearly after that?

@kibertoad
Copy link
Contributor

I'd say it depends on the pace of innovation. If there is a surge in development activity and urgency in de-messing Express, it may be beneficial to have more frequent releases initially (e. g. twice a year), but move to a slower cadence after things quiet down somewhat.

@sheplu
Copy link
Member

sheplu commented Feb 29, 2024

One main point is also "how long we want to maintain a major version". Only keeping express 5 for a few months is not something that we can do, it would hurt the ecosystem.
On the other hand the work that would be critical for v6 (from the first Express Forward issue) will certainly require months and months of hard work, depending on the list of changes and supports we want to do.

So without giving a timeline, if we were releasing express 5 today

  • v4 will still have some update (security mostly) for a good time (a year I think)
  • v5 will be actively maintained until v6 and then be in some maintenance mode (between a year and 18 months, we could also announce maintenance mode before the release of the v6 to "gain" some months)

So between new version and support, we don't want to have to many version being live at the same time, it would be hard to maintain all of them. One major version a year would be the lowest amount of time, I would target something closer to 2 years

Taking fastify as an example here

  • minimal 6 months for a major version
  • an additional 6months for security

@wesleytodd
Copy link
Member Author

I for sure agree that two majors a year is too fast even if we need a flurry of activity. I think that since we landed the guidance doc we should change from using this thread to opening proposal PRs to dig into details. Ideally IMO that doc should evolve to contain the planned "schedule", a comparison of Express version support windows to Node.js LTS versions, and also likely more details on how we will make the go/no-go decisions on breaking changes.

@wesleytodd
Copy link
Member Author

wesleytodd commented Feb 29, 2024

Probably sent that too soon.

Only keeping express 5 for a few months is not something that we can do, it would hurt the ecosystem.

Yeah I think we will be stretched thin even with both majors for a year. I agree and think we should try and commit to supporting v5 for two years. Think about the transition here, folks are not at all used to having major updates, so we may have a really long tail of updates here. Just because we have excitement (which is awesome) we should make sure we dont hurt the ecosystem with our efforts.

So without giving a timeline, if we were releasing express 5 today

And to address this point from @sheplu, I totally agree the schedule and docs should explicitly cover how long security release will be done (I feel like this was partially covered but I dont have the doc in front of me).

@kibertoad
Copy link
Contributor

Very fair points. Waiting two years after v5 lands sounds overly long, however. What do you think about Node model, with non-LTS odd releases that are only supported for one year, and even LTS releases with a 2 year support?

@wesleytodd
Copy link
Member Author

Very fair points. Waiting two years after v5 lands sounds overly long, however.

Yeah, I am not sure I feel like we would do 2 years for ever, but having to roll out node LTS version at work I can say that for larger orgs longer is for sure better. Even the Node LTS schedule is too fast for Netflix and I imainge there are many orgs with even tighter restrictions and slower pace.

What do you think about Node model, with non-LTS odd releases that are only supported for one year, and even LTS releases with a 2 year support?

I do sort of like this. I can get on board if we could call v5 special and ear mark it for longer support while also saying we would do 1 major a year (max) for the rest with an alternating LTS version every other.

@kibertoad
Copy link
Contributor

Agree that there are big corpos that move slow, but I wouldn't artificially slow down releases for their sake. Existence of a release doesn't mean they must upgrade. For that matter, having two separate releases with half the breaking changes in each make more incremental updates possible, which is typically easier and less risky.

@jonchurch
Copy link
Member

jonchurch commented Feb 29, 2024

I have so many opinions and cannot capture them all right now.

I am not caught up on the thread entirely so apologies if Im retreading.

Specifically to cadence of bringing innovation:

can we consider a "future flag" approach? One LTS version active, one "next" version under development, bring stable features from "next" to LTS behind feature flags. Consumers can use future looking features in production, if they are bold, without having to wait for the next major to cut. When it cuts, they drop the feature flags and either don't have to rewrite any code, or have to rewrite less bc they are on the newer API already.

I think Remix does this. It is maintenance burden FOR SURE, but if we can pull it off it's like magic ✨✨

@ljharb
Copy link
Contributor

ljharb commented Feb 29, 2024

Existence of a release doesn't mean they must upgrade

while this is technically correct

image

… in practice this means that they'll be screwed out of security updates and bugfixes, since backports are annoying enough that they're rarely done.

Regardless, I would strongly suggest (as much as is feasible) requiring all breaking changes to be shipped non-breaking, behind an option, and to have the next major simply flip the default (ideally, preserve both behaviors), since that makes updates much easier and less risky.

@kibertoad
Copy link
Contributor

@ljharb LTS strategy explicitly requires backporting important fixes to all maintained LTS lines.

+1 on shipping breaking changes under a flag. Can someone make a PR adding that to the doc?

@wesleytodd
Copy link
Member Author

I wouldn't artificially slow down releases for their sake

I am not saying artificially slow things down, but that is not itself a clear direction. I think the goal is a pace which balances both for us maintainers and our variety of users. If 2 years is the right pace for enterprise and 6 months is the right pace for hobbyists who want to use bleeding edge features we should shoot for the goldilocks zone.

For that matter, having two separate releases with half the breaking changes in each make more incremental updates possible, which is typically easier and less risky.

Yep! But the key is that that incremental thing cannot mean shorter support of security and bug fixes. That's the balance.

can we consider a "future flag" approach?

We can!! But there is a lot to consider with that and it will matter what the feature is. So I think we shouldn't hinge this decision based on if or if not we do feature flagging. And this is basically what @ljharb said right after:

requiring all breaking changes to be shipped non-breaking, behind an option

So with that I think we should "Can someone make a PR adding that to the doc?" as @kibertoad said.

@wesleytodd
Copy link
Member Author

wesleytodd commented Apr 10, 2024

I have not updated this thread in a bit, but I think my position has changed. After many discussions I believe my tipping point on this issue is the following:

Nothing today is stopping folks on v4 from using latest Node.js versions. Additionally, nothing we would change in v5 would block them either.

This means that "dropping support" for these node versions does not impact their ability to upgrade.

And to that point, this is what I am going into our discussion today for the working session thinking:

  1. I propose we keep running the tests for old node versions until they start failing
  2. I propose we set an engines field which is maximally permissive in v5 but plan to strongly restrict it with v6
  3. I propose we explicitly document that starting with v5 we only "support" node LTS lines at the time of release (and we need to clearly define what support means, as per all the comments above)

I think after we discuss in today's meeting we need to move all this stuff into the doc @kibertoad started in #199. But after that I hope we can close this issue out and start acting on this goal.

@ljharb
Copy link
Contributor

ljharb commented Apr 10, 2024

It impacts their ability to upgrade if other things are keeping them stuck on old node versions, which is often the case for many applications - think sass, gulp, and anything using C++. There's a reason that node 16 and 14's usage, each, are larger than node 18's downloads, and node 12 has more downloads than any of node 19+ combined.

@wesleytodd
Copy link
Member Author

Yeah this was my original concern, but similar to what I said above with "that is node.js' job" I think "that is sass/gulp/etc's job". Nothing we do here will stop them from keeping on Express v4 for the next few years, and since we are not going to out of our way to break them I don't see us gaining anything from this "lowest common denominator" approach (us or our users stuck in the way described). As in, nothing we do will help them either way afaict.

@UlisesGascon
Copy link
Member

I created a PR to land the changes expect from our last TC meeting expressjs/express#5595

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
top priority Issues which the TC deem our current highest priorities for the project
Projects
None yet
Development

No branches or pull requests

6 participants