-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Maintaining Emitted Backwards Compatibility Across Minor Releases #51392
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
Comments
My statement that downlevel-dts doesn't support inline types was based on a conversation I had with Daniel, but it seems like that maybe we were wrong. I still think the primary point stands as we'd rather maintain backwards compatibility by disallowing feature use rather than by making use of |
mark |
I would also +1 to Jason's remarks above - adding features to d.ts files in minor releases forces producers to never minor bump typescript. This is incredibly unfortunate because we want to be on the latest version at all times, especially for producer code, where newer versions might catch new bugs sooner. It would be really ideal to consider d.ts typing output to produce new syntax only in a major release. Add a feature? major release. Don't want to major release yet? Find a way to represent the syntax in the current major release grammar and emit that. In the case of inline I'd also love to see a comment emitted in typings indicating the typings version. For example, something like: // TypeScript compatibility: 4.0.0 At a glance, any producer could know exactly what minimum typescript version is expected. In the example above I know if I am using any version of TS 4.x or above, my compiler will accept the syntax. It might even be useful metadata for the compiler, because now there is enough information to have a better error than "syntax error". Example:
|
I think this is effectively a duplicate of #36207.
|
Since "minor release" is often mentioned here, I'd like to add something. TypeScript does not follow semantic versioning. The version increment is always the same: Minor until 9, then major. There's no deeper meaning behind it, like "no breaking change". Every TypeScript release should be treated as a major release, because that's what it is. |
The TS team has also repeatedly asked us to use new versions as quickly as possible. Having a major breaking release every few months is a tax that very few large projects can afford, particularly when it spreads downstream across the whole org and quickly spirals out of control. This just effectively means people will upgrade TS far less frequently, and that is a problem that also compounds.
Related to above, these replies are basically justifying TS breaking releases every few months because of a secondary optional compiler that is available. Our repo has literally hundreds of packages that compile in TS, and that's just in our intermediary repo. Adding what is effectively another compiler has significant CI performance and maintenance costs, and that's not even counting whether or not the downlevel compiler is guaranteed 100% compatible and supported as well as TS itself. In our case, it'd be much more preferable to maintain type compatibility by disallowing features that break it. |
The comment I quoted was from the team lead himself so Iβm not the person you have to convince. |
Sure -- previous TypeScript versions are available on npm, and you can install+run them to see if they "work" given whatever definition of "work" is operative in your environment. Regarding semver, even under a broad definition of what constitutes a breaking change, this isn't one. It's allowed to add new features in minor releases, by definition. It doesn't make any sense to construe this as a breaking change because prior versions of the software can't consume code written using that new feature. We've been quite careful to make it so that declaration emit, whenever possible, doesn't rely on new features unless you used those new features in your code.
This is a great opportunity for a lint rule. Since you're both internal I'd invite you to stop by office hours today and we can chat more. |
There is nothing particularly niche or customized about our environment to justify us taking on this tax. We are merely consumers of a TypeScript environment in a broad ecosystem of upstream and downstream TypeScript consumers, all who adopt new versions at their own cadence.
We could have a discussion about the semantics of semver, but I think it avoids discussing the practical implications of this approach which I've touched on above. The fact is, as a large TS project in an ecosystem of other TS repos and projects, there is significant friction in adopting new TS versions, regardless of semver semantics.
I worry again this is a tax we are being asked to pay simply for being relatively nimble in adopting new versions. I'm not even against it outright, but as I alluded to in my post, we're not sure which features cause problems to write eslint rules against. Ultimately that just means we don't adopt TS and use any new features at all. Knowing which features to avoid would be a significantly valuable first step. |
I think it'd be valuable for us to more explicitly call out in the release notes which new syntaxes will appear in .d.ts files if you use them. What folks do with that information is up to them, but we should be clearer about it. |
I think extra release notes are great but it's ignoring the fact that if a producer upgrades ts and considers using said new feature, they now have mental math to jump through. When was this feature introduced? What is this particular package's minimum typescript requirement for consumers? What versions are our consumers using? When new PRs come through especially for OSS projects, we also have this mental math in reviewing changes. When consumers complain that we merged some PR that broke them, do we feel bad about ourselves, that we missed this? Do we blame the consumer for not being on the latest typescript? Do we blame the typescript team? You can see this spiraling into chaos when you consider the scale at which we are using TypeScript across producer and consumer boundaries. We recognize downlevel-dts is an option to work around the chaos. It costs not just engineering work to integrate, but CI build time which is a HUGE problem at scale. It would be desirable to not add another serial build step requirement, another AST parse, to every package in the world that uses typescript. Plus there are concerns that it's not in the typescript repo, isn't releasing on the same cadence which might lead to it falling behind (not saying it is, just saying it would be better to be releasing as a ts feature.) Lint rules are another way for us to safe guard against feature usage, but it's a HUGE tax, and we're losing the benefit of upgrading at all with this! We want to use inline types. We don't want to drag our feet on an upgrade. We don't want to upgrade and write lint rules to avoid using the new features of the upgrade. That makes no sense. A better approach would be: keep adding new syntaxes. Keep progressing. Add release notes to describe the new features. But either save the new volatile d.ts emissions for a major release, or even have a tsconfig setting that says to run downlevel-dts under the hood of typescript to emit compatible typings, but give us something that we can guarantee we aren't breaking partners by using the language. This would likely mean you could avoid an ast parse, and downlevel-dts behavior would be a first class well maintained citizen supported in the same release pipeline. |
I don't understand what you're proposing here. Let's say we add some new type feature, like export const m: kinda boolean = probably; In the .d.ts file we should then do what? export const m: /* what? */ |
Maybe worth noting that the mentality of βextra tools are a tax, this would be better built-inβ is a potential slippery slope w.r.t. where to draw the line, and being an all-in-one tool is explicitly a non-goal for TS: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
|
Good question! What would Moving the So my first thought would be: move My second thought is that it should be configurable. Maybe tsconfig could have a setting for what sort of typings to emit, something like
This is just an idea. |
This is a fair point and indeed a slippery slope. I think downlevel-dts has a good reason to just ship with tsc though, beyond the extra reparse costs. We want the tranform to be inline with the new features. Something like: add a new feature that breaks typings parsing? Cool, you need to consider how this gets downleveled to maintain compatibility. |
I mean, we did consider this, and wrote
etc. If CI speed is the most important thing, which it sounds like it is, then |
Even if done in parallel, there is still a CI cost in terms of compute time. My primary concern in adopting I think your idea of explicitly call out in the release notes which new syntaxes will appear in .d.ts files if you use them is a good one. I'm curious if that could tie into a tsconfig option (as David mentioned) that would help us avoid breaking features entirely. We don't necessarily need emit-compatible source that |
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
That's an even better question. I think downlevel-dts would become abandonware, to be honest. |
Bug Report
π Search Terms
maintain emitted backwards compatibility ts minbar across minor releases
π Version & Regression Information
β― Playground Link
Playground link with relevant code showing
d.ts
emitted inline type imports that break consumers < TS4.5π» Code
π Actual behavior
Downstream consumers using < TS4.5 break upon
d.ts
output containing inline type imports.downlevel-dts
does not appear to support making inline type imports backwards compatible.π Expected behavior
I wanted to create this issue primarily for collaboration, but I'm creating it as a bug as I think at the very least there is a gap in downlevel-dts.
TS regularly adds new features like inline type imports that make emitted TS output incompatible for any downstream consumer using any older version of TS. TypeScript considers these features opt-in and therefore aren't listed as a breaking change. However, since devs don't easily know which minor release features break backwards compatibility, it becomes untenable to use any new feature introduced by any TS minor release for fear of breaking any downstream consumer that isn't at the latest possible version. This ends up introducing significant friction in maintaining TS update velocity, either because we don't want to upgrade as frequently or because we introduce issues using new TS features that only become known as downstream consumers use our emitted source.
While there are tools like
downlevel-dts
it doesn't appear to cover this specific inline type import issue, so that wouldn't fix our issue in this case.It seems there should be some test suite in TS that determines whether the emitted output of each new minor release feature is backwards compatible. At the very least, this could feed into downlevel-dts to uncover gaps in the downlevel tooling. It would also be really helpful if this could feed into a minbar configuration that would indicate to devs whether or not the features they are using break backwards compatibility. If I could tell TS which TS version I want to maintain compatibility with and have it fail compilation if the emitted output is not compatible, that would be ideal.
To summarize, as things currently stand, our options are:
downlevel-dts
to uncover gaps such as inline type imports not being transformed.The text was updated successfully, but these errors were encountered: