Suddenly streams that were being passed around could not be passed to functions that were expecting the old type of stream. So just making that change was a breaking change for lots and lots of people. It still probably had to be made, but you have to be very careful about it. And it takes a lot of effort to make sure that you version your types properly.
I consider a tool like API extractor. It's incredibly full featured. It is a little bit complex to configure, but it can help with ensuring that your type contracts don't change, that you're able to adhere to when you're releasing new changes to types. And obviously, there are things you can do when you are making changes to ensure that they're not full on breaks, but things are a little bit gentler. So, you can, for example, just deprecate a type if you're renaming it. So, users do have some time to change their usage. And you can do something similar, of with functions, rather than just change the type of the function. Again, this might not be necessary. But in some cases, it could be. You can add an overload, so that the function continues to have the previous signature, but it also has a new signature, and it handles both. So, there are some things you can do, I think, to reduce the impact on you as a library maintainer from having to release new minor or major versions when you're changing your types.
It's also worth saying that if your types are your code, if you're thinking of them as your API and a contract with your users, that it's really important that you're only exposing the types you intend to. There'll be lots of internal types, types that you're using for utility purposes. There might be utility code that is just internal. By default, TypeScript does something which I do find unaccountable, and that is it obviously renders the full structure of your project out in declaration files. So, users can, by adding the path to your source files, access the types and the declarations for every single function and type that is exported in any one of your source files, even if it's not exported from your final entry point and bundle. So, using something like rollup-plugin-dts or dts-bundle-generator means you can actually roll that up so it won't be accessible from your final index.dts. That's a hugely useful thing in conjunction with everything else I've been talking about in terms of testing and versioning your types. There are plenty of build tools that will do this for you already, including unbuild, which I mentioned earlier.
And finally, it's not just types as documentation or types as code, but coming to the real point of it, types as truth. Forgive the philosophical or epistemological point. I think this is what types are all about. They're about exposing the reality of the project, of your library, of how it works and what it does. And the corollary, is that we have to avoid the opportunity for lying to ourselves and our users. Because we do this all the time, and I am very prone to this, it's really easy to put an as or to type something as any. It's very possible to turn off strict null checks and just enjoy the fact that you're not having to...
Comments