Library versioning: Typescript / ES5 - javascript

I want to write a small runtime environment independent library for managing general trees and I have a doubt on how to package / distribute the library after it's finished.
I have the following in mind:
Written in Typescript
Create Typescript definition template (d.ts)
Transpile to ES6 / ES5 and distribute as a node module
Possibility to use as a node module on the server, but also in the browser (commonjs / AMD compliant, but also just working without module loader / build step);
I've looked into the dist folder of ImmutableJS on Github to see how they do it, and got a bit confused:
It is written in ES2015
There is a TS definition template defined
But the ES5 version (immutable.js) does not seem generated (it looks like hand-written code). https://github.com/facebook/immutable-js/blob/master/dist/immutable.js
Hence, I wondered if they wrote the library in both ES2015 / ES5 (in parallel) and what influence this has on maintenance / syncing versioning.
It appears as if the ES5 version was written specifically for the browser (non-TS). Can you give me some pointers on what's happening here?

Related

How to make 4.X Typescript project compatible with older version of Typescript 3.X?

How to make package built on top of TS 4.X compatible with 3.X? Like, if you have newer version -> use new features, else -> use any or unknown or whatever is supported in older version.
Is there any possibility to use directives for that purpose?
Preface
This answer assumes the question is about taking source code (.ts) of a project written using TypeScript 4.x constructs and making type declaration files (.d.ts) emitted from them compatible with a TypeScript 3.x compiler for the benefit of the users of the package- as opposed to writing source code that uses 4.x constructs and somehow making it compatible with a 3.x compiler (the way the question is phrased is ambiguous with respect to this). I make this assumption because:
If you decide you want to use language features in your source code that aren't supported by older compiler versions, you are (whether you realize it or not) making a decision to drop support for building the project using those older compiler version. If you have a strong reason to want to support building the project using those older compilers, then I'm pretty sure you just have to not use those newer language features (or somehow convince the maintainers of the compiler to backport those changes to older compiler versions, which I think is pretty rare).
I'm not personally aware of any reasons not to upgrade your compiler version unless you have very strict security policies and require your dependencies and build tooling to be audited. I'd wager that that's pretty rare in the JavaScript development scene where the landscape is known for changing rapidly.
downlevel-dts
Note: TypeScript has a feature to "downlevel" the JavaScript it emits to convert language constructs from later versions of the ECMA Script standard to constructs that work in older versions. (see the compileOptions.target field of tsconfig).
As far as I know, TypeScript itself doesn't have such a feature to downlevel the typings files it emits (including triple-slash-directives at the time of this writing), but Nathan Sanders (a maintainer of Definitely Typed) maintains an open-source project, downlevel-dts, to downlevel .d.ts files which can downlevel typings all the way down to typescript v3.4 syntax.
package.json.typesVersions and semver-ts.org
The "'Downleveling' Types" section of semver-ts.org explains how you can script downleveling types for each typescript version where new non-backwards-compatible language constructs were introduced and how to tell a package-user's compiler which version of the types to use and where to find them:
When a new version of TypeScript includes a backwards-incompatible change to emitted type definitions, as they did in 3.7, the strategy of changing the types directly may not work. However, it is still possible to provide backwards-compatible types, using the combination of downlevel-dts and typesVersions. (In some cases, this may also require some manual tweaking of types, but this should be rare for most packages.)
The downlevel-dts tool allows you to take a .d.ts file which is not valid for an earlier version of TypeScript (e.g. the changes to class field emit mentioned in Breaking Changes), and emit a version which is compatible with that version. It supports targeting all TypeScript versions later than 3.4.
TypeScript supports using the typesVersions key in a package.json file to specify a specific set of type definitions (which may consist of one or more .d.ts files) which correspond to a specific TypeScript version.
The recommended flow would be as follows:
To avoid copying too much from off-site material (plagiarism), I'll summarize the steps in my own words (go read the source for the full steps with examples):
Install downlevel-dts as a dev-dependency (and some other helper tools).
Call downlevel-dts to downlevel types to whichever older type declaration versions you want to support (this can be scripted).
Update your package.json to register call your script after generating types for the more recent type declaration verion.
Register your generated older-version type declaration files in your package.json file using the typesVersions field.
Make sure the generated files are included with your package's files (update the files field, or whichever similar fields you are using).
limitations of downlevel-dts
Note that there are limitations. The following is a quote from downlevel-dts's readme:
Note that not all features can be downlevelled. For example, TypeScript 4.0 allows spreading multiple tuple type variables, at any position in a tuple. This is not allowed in previous versions, but has no obvious downlevel emit, so downlevel-dts doesn't attempt to do anything. Be sure to test the output of downlevel-dts with the appropriate version of TypeScript.
Problematic aspects of other proposed solutions:
"Use an older typescript version to emit your typings"
This assumes the asker of the question is using the TS compiler to emit typings from .ts files and doesn't work if they are maintaining them by hand, which is often the case for very large projects that were first written in JS and don't have the bandwidth to migrate to TS.
This probably requires you to make your .ts source code to not use TypeScript language constructs that were introduced in a version newer than the compiler that you have to use to emit typings in the TypeScript language version you want to emit. This is not ideal because it may be much cleaner to write code using newer language constructs, or impossible to do something with just older constructs.
"Maintain typings for both TypeScript language version"
This seems to make the opposite assumption: That the project maintains manual typings files for manually written JS code instead of being transpiled to .js and emitting .d.ts from .ts files.
This is a big maintenance burden (it's already a big maintenance burden to manually maintain .d.ts files!). It may be acceptable for small projects, but not for libraries with a large or complicated API surface.
There is no way to switch between supported and unsupported features in TypeScript.
For instance, if you have a library which is built on top of TS 4.0 with variadic tuple types there is no way to use it in a package where TS 3.0 is used.
However, you can maintain two versions of your typings: before TS4 and after TS4. For instance, take a look how lodash or react maintaince several versions of typings.

How does a packages typescript code is consumed by other packages?

When a package with typescript code is imported. will the consumer use typescript or transpiled code (e.g. typescript to ES5).
Scenario #1: If the consumer uses transpiled code (which is not in typescript). How is VSCode able to recommend auto completions from the packages types?
Scenario #2: If the packages are served with typescript files.
What happens if the consumer is not using typescript?
How does the bundling at the consumer end happen?
TL;DR
Scenario #1: This is the most common scenario for public NPM packages, editors use external .d.ts files that contain type declarations
Scenario #2: I've only seen this with internal libraries
1) You'd need to compile the TS by yourself, but that would probably not be necessary because there will almost always be a compiled-to-js version of the library, too
2) This could go many ways, best case is that the library gives you a pre-optimized JS that is just bundled with your code, worst case is that you have to make sure you have all the right dependencies installed for the library to be able to be compiled with your code and they need to be compatible with the dependencies you're using yourself. In my opinion this isn't worth the hassle, having type declarations for NPM packages is sufficient for the level of type safety TypeScript can provide.
The Long Version
There are dozens of variants how people distribute their TypeScript based code via NPM. Most big libraries I know ship their compiled (and often minified) JS and add .d.ts declaration files for editor support - these may be hand-written or produced by using tsc from a codebase. VSCode uses its TypeScript language server for JavaScript files, too, so in many cases it doesn't even need additional declaration files to provide you with basic autocompletion. Libraries can declare a types field inside the package.json, VSCode and other editors will find types there. There is a rather large community that maintains mostly inofficial type declarations for most libraries on definitelytyped - you may be out of luck for obscure ones.
A few examples
Material UI
Is written in JavaScript
Has hand-written TypeScript declarations (which may or may not be accurate)
React
Is written in JavaScript
Provides no TypeScript declarations with the NPM package but VSCode knows where to find inofficial external ones
Angular
Is written in TypeScript
Uses the deprecated typings field to point to its type declarations
jQuery
Is written in JavaScript
Again, has no provided type declarations but your editor probably knows how to fetch inoffical ones
Seneca
Is written in JavaScript
Has inoffical type declarations that are two years old at the time of writing - good luck finding out whether the API really hasn't changed
Important take-aways:
There is no single way people do things in the JS ecosystem and by extension the TS ecosystem
Most type declarations you're using are not official ones but the ones maintained by the community, mostly on definitelytyped
The types might be lying to you (may be out-of-date, just be plain wrong or for an older TS version)
TypeScript is a superset of JavaScript, it comes with all the same gotchas by definition
All types are erased at runtime, in the end you're running plain JavaScript with all that this entails

Which I should select for Building library ? ES5 or ES6

I want to build a simple library in JS (vanilla). I am a bit confused about whether to follow the class-based paradigm or prototypal-based paradigm. ES6 is now in the mainstream, even though ES5 is being used.
What things Should I consider
The best way to do this is to write your source code using the latest ES6+ features. Now your javascript library may be consumed by three different types of clients:
Browser
NodeJS
Another library
1. Browser - For browser, the best option is to have the source code transpiled into ES5 and then build in IIFE form.
2. NodeJS - The best option is to transpile it to es5 and build in CommonJS(CJS)format.
3. Another library - The best option is to transpile the source code to es5 but still retain the es5 modules (export/import). You can export the build as esm modules. This helps the bundling tools for better treeshaking while using your library as a dependency.
The mostly used js bundling libraries: Rollup, Webpack and Parcel supports them. You can check them out for more info.
Happy coding =)
You could use ES6 and transpile the code with babel to ES5 for backward compatibility.
Take a look at this boilerplate for example code.
npm-module-boilerplate

Why do some projects that use typescript/webpack also use babel to finish compilation

I've noticed some web projects using typescript and webpack also use babel to finish off the compilation. For example, they use ts to compile to ES2015 and then use babel to compile to es5. Why not just use ts directly to compile to es5?
Is it in the case the project also has js that needs to be compiled so they just use babel for everything? Or what am I missing?
Thanks.
There's a few possible reasons for this.
They're using Babel to automatically polyfill - TypeScript only performs syntactic transformations, leaving the user to figure out what runtime libraries they'll need to be around (e.g. Promise, Symbol, etc). This allows you to decide which implementation of these polyfills will work the best for you, but it can be a pain. Babel spares you the burden of thinking about that. It's a tradeoff.
They need it for a custom transformation - TypeScript has a transform pipeline, but it's only accessible when you use the TypeScript API at this point in time. If you're already using Babel and want to start using TypeScript, but you're already using a transform, this is a reasonable compromise.
It was created when TypeScript didn't support compiling generators to ES5 (or even back when TypeScript didn't support async/await in ES5) - TypeScript has supported async/await in ES5 since 2.1, and has supported generators behind the downlevelIteration flag since 2.3. Before that, users often relied on Babel to pick up the slack, but Babel isn't needed here anymore.
It was created before Webpack 2 and the project used a specific way of importing modules - TypeScript has an allowSyntheticDefaultImport option which tells TypeScript that default imports can be used to import certain modules. Babel supports this behavior, but Webpack didn't until Webpack 2 came out. Babel isn't needed here anymore for newer versions of Webpack.
This might not be the complete set of reasons, but it's a few I can think of off the top of my head.
Why not just use ts directly to compile to es5?
That is what I do.
About mixing Babel and TypeScript
There is no flaw in using the both together. Since both do:
(non js OR js) => standard js
You can do
(non js OR js) => standard js => es5
Either by TS -> JS -babel> ES5 or Babel -> JS -ts> ES5
The reason people do it is for varied syntax support : https://kangax.github.io/compat-table/
Personally
As mentioned. I don't use Babel as I TypeSafety is big for me and don't need to use syntax that isn't yet type safe 🌹
Typescript is the evolution frOM ES2016 onwards. Typescript helps the developer from c# and java background to become javascript developers using various tools. Visual Studio code, WebStorm, Sublime etc.
Why: We can not use Typescript alone for converting ts to ES5
Compiling to ES5 with TypeScript is not as complete as it is with Babel. Some modern language features, such as Array.prototype.find, cannot be compiled to ES5 with TypeScript.
Here is the link that will help you out: https://www.stackchief.com/blog/TypeScript%20or%20Babel%3F

Babel and Browserify / Webpack confusion

Quick question. I am a bit confused about ES2015(ES6).
Let's say I use Babel to compile to ES6 Javascript to compliant ES5 for current browsers.
The import/export functions are already available in ES6 by using Babel. So why would I need something like Browserify or Webpack if I were to simply use these just to bundle my modules, when ES6 could do it for me?
Everywhere I go I see people using Babel in combination with Browserify or Webpack. Although I know something like Webpack can be used for more, but I wonder if it is also possible to bundle files using the ES6 syntax.
I might be totally in the wrong here and I might have gotten lost in the Javascript Jungle of 2016, so I hope someone can clarifty this for me.
Edit
Am I right to assume that the native ES6 import / export functionality simply does not bundle files? From what I have read so far I think you still need to include all the separate Javascript files but you simply import modules into each-others namespace by using the native import functionality?
Yes, using babel to transpile your ES6 imports into ES5 will work.
However, one advantage of using webpack is that creates one static file to be served up in your production environment.
Pre-ES6 has no native module system, so there are multiple systems constructed in userland code (e.g. CommonJS / Node modules and AMD). Those are what Babel converts ES6 module syntax to (and yes, you're correct that ES6 module syntax has no native bundling story anyway). Browsers have no knowledge of those userland APIs. Node implements its module system by wrapping a "module" in a function that injects require() etc. In a browser require() would just be a reference error. Browserify (or another bundler) makes it work in the browser, and bundles a whole dependency graph into a single script. So if the code is for the browser you're likely going to want to bundle it. If it's for Node you may not need to.
The import/export functions
Not functions, declarations.
if I were to simply use these just to bundle my modules, when ES6 could do it for me?
I wonder if it is also possible to bundle files using the ES6 syntax.
Am I right to assume that the native ES6 import / export functionality simply does not bundle files?
Yes. There's no native way to bundle ES6 modules. You can transpile ES6 module syntax to something like Node modules and bundle those.
From what I have read so far I think you still need to include all the separate Javascript files but you simply import modules into each-others namespace by using the native import functionality?
It's important to realize that while the syntax is standardized, a lot of the behavior isn't. There's a Loader spec under development to specify how modules will actually be located and loaded.
See also https://stackoverflow.com/a/33044085/1034448.

Categories

Resources