I have been developing an Angular app using Typescript, and I have come to realise I need to pay more attention to the tsconfig in regards to compilation (transpilation).
Currently in the source code I am using some es6 features (such as Array.prototype.find) and the TSLint-er is picking up these as errors.
I'm trying to reconfigure my tsconfig so that it allows me to use es6 features but transpile to es5. In doing this, I'm failing to understand what the "target" property actually is. What does the "target" property mean?
Does "target" represent what the desired, transpiled output will be? Or does it declare what the Typescript syntax should conform to in order to be transpiled?
TypeScript is a superset of ES6, so you’re essentially writing TS code using ES6 version of JavaScript. However, when compiled, the resulting JS code can be in ES5 or earlier. You need to define which version of JS the compiler should transpile into. This can be set using target option:
{
"compilerOptions": {
"target": "es6"
}
}
You can read more about configuration here.
However, it also is used for validation indirectly. This parameter defines which libraries are used during compilation. If you specify target:es5, it uses ES5 library which doesn't contain Array.prototype.find. You can manually set the library you want to be used:
{
"compilerOptions": {
"lib": ["es6", "dom"],
}
}
In this case you will not have an error even if you specify es5 as a target.
Related
Latest Update (2022-06-06): TS 4.7 supports "exports"
tl;dr
// package.json
"type": "module"
// tsconfig.json
"module": "node12" // or "nodenext"
Update: TS 4.5 does not support "exports" (also see this issue):
... support for Node.js 12 has been deferred to a future release, and is now only available as an experimental flag in nightly releases. This was not an easy decision, but our team had a combination of concerns around ecosystem readiness and general guidance for how/when to use the feature.
[2022-04-01] The feature is still not available in TS 4.6.
Original question:
I am wanting to take advantage of the new-ish "exports" feature of NodeJS/package.json so that I can do the following:
"exports": {
".": "./dist/index.js",
"./foo": "./dist/path/to/foo.js"
}
And users can do the following:
import { foo } from 'my-package/foo';
Typescript 4.5 should support the "exports" field, yet it does not seem to work. I am building a simple package using TS 4.5.2, and I am consuming that package in a project using TS 4.5.2. I have looked at other SO questions and this github thread and this bug report but can't seem to find a consensus on the issue and whether it should work today.
Note 1: I am still able to import using the more verbose syntax:
import { foo } from 'my-package/dist/path/to/foo.js';
Note 2: I have also tried the object notation for exports, to no avail:
"exports": {
".": { "require": "./dist/index.js", "import": "./dist/index.js" },
"./foo": { "require": "./dist/path/to/foo.js", "import": "./dist/path/to/foo.js" }
}
Question(s):
Is this feature ready to be used with typescript projects today? If not, I just want to know.
If yes to #1, what am I missing? Specifics about tsconfig would be useful for both the source project and consuming project. The TS compiler complains about node12/nodenext being used for either the module or moduleResolution fields (I am definitely using TS 4.5.2).
I've been looking to use nested folders for a design system package we created at Pipefy, and after deep research, I found how to do it.
The package exports React components, and we use to import them like this import { Button } from '#mypackage/design-system;
Later on, we've added tokens to our design system library, like Colors, Spacing, and Fonts, but we don't want to import everything from the index, it isn't productive.
After some exhaustive research, I found how to export nested folders using TypeScript. My purpose is to use tokens like this import { Colors } from '#mypackage/design-system/tokens;
To use your TypeScript lib like this you should use the typesVersions inside the package.json file.
Here I use it like this
"typesVersions": {
"*": {
"index": [
"lib/components/index.d.ts"
],
"tokens": [
"lib/tokens/index.d.ts"
]
}
},
It worked like a charm for me, it would work for you too!
Without knowing what error you are getting, or in what other way TypeScript doesn't seem to be working for you (not sure why you would not want to share such crucial information), I can tell that your exports section appears to be missing types information. Typically, if your .d.ts files were located next to their respective .js files, your exports section would look like this:
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./foo": {
"types": "./dist/path/to/foo.d.ts",
"default": "./dist/path/to/foo.js"
}
}
I'm posting my own answer because there has been a lot of confusion around this topic.
NodeJS has supported exports since v12.7.0 (Jul. 2019)
When I asked this question (Dec. 2021), NodeJS had supported the exports field for nearly 2.5 years. It seemed reasonable to assume that Typescript supported it.
Typescript did not support the exports field when I asked this question.
When I asked this question (Dec. 2021), the exports field in package.json was not supported by the current version of Typescript at the time (v4.5). This was particularly confusing because the TS 4.5 beta announcement said that it would support the package.json exports.
Typescript 4.7 (Jun. 2022) finally supported package.json exports
After much testing and quite a bit of secrecy, typescript finally supported the exports field for package.json. Wine and whiskey were consumed with this announcement.
Using typesVersions in package.json is not the solution
Several people suggested using typesVersions - but thats a completely separate feature which is specific to typescript only (read more about it here). The exports field in package.json is supported by ANY npm package - typescript or not.
What you need in order for this to work
Your typescript project must be using TS v4.7 or later
Your tsconfig should be using moduleResolution: node, node16, or nodenext.
You don't have to set moduleResultion if you are using module with a value of CommonJS, ES2015, ES6, ES2020, or ESNEXT
TypeScript only respects export maps in package.json if you use "moduleResolution": "NodeNext"(or "Node16") instead of the widespread "moduleResolution": "Node". (I guess "moduleResolution" defaults to the same value as "module", but it's hard to find documentation of this?
See: https://www.typescriptlang.org/docs/handbook/esm-node.html
However a number of TS libraries out there (including many of Microsoft's own) have errors with "moduleResolution": "NodeNext" right now because of things like relative imports without explicit file extensions.
According to MDN Web Docs Array.prototype#at, is a valid method. But for some reasons, TypeScript refuses to compile, stating that it does not exist.
public at(index: number): V {
index = Math.floor(index);
const arr = [...this.values()];
return arr.at(index);
}
Console output from tsc:
I can't use bracket notation, since the method is meant to be able to handle negative numbers. I've tried lots of things, including setting the target in the tsconfig.json file to ESNext, ES2021, and ES6 but to no avail. The lib option doesn't help either.
json
// tsconfig.json
{
"compilerOptions": {
"target": "ES2021",
"outDir": "./dist",
"declaration": true,
"declarationDir": "./typings",
"lib": ["ES2021", "ESNext"]
}
}
What can I do? Am I doing something wrong?
According to MDN Web Docs Array.prototype#at, is a valid method.
MDN does not get to decide what is part of TypeScript and what isn't. The TypeScript developers do that.
Whether or not MDN says something is a "valid method" is relevant to Mozilla, and only Mozilla, but has no bearing on TypeScript.
But for some reasons, TypeScript refuses to compile, stating that it does not exist.
That's because the method doesn't exist in any ECMAScript version supported by TypeScript.
I've tried lots of things, including setting the target in the tsconfig.json file to ESNext, ES2021, and ES6 but to no avail.
That's because the method doesn't exist in either ECMAScript 6 or ECMAScript 2021. It does exist in ES2022 (which is what ESNext is at the moment), but it was only added eight weeks ago, and thus after TypeScript 4.4 was finalized (and possibly also too late for TypeScript 4.5).
The lib option doesn't help either.
Again, that's because the method does not exist in any library version supported by TypeScript at the moment.
Even in the current main branch, which is going to become TypeScript 4.6, lib/lib.esnext.d.ts only corresponds to ECMAScript 2021 + the latest Internationalization extensions.
What can I do? Am I doing something wrong?
You can wait until the method actually becomes part of a released version of ECMAScript and/or TypeScript.
Array#at is indeed a new method available only in ESNext or by using a polyfill. If you're using latter, you can just augment global array interface like this:
declare global {
interface Array<T> {
at(index: number): T;
}
}
When tsconfig.json has the following
"target": "es5",
"lib": [ "es6", "dom", "es2017" ]
it doesn't seem to convert es2017 constructs to es5. For instance, the following will fail on IE11:
var foo = [1, 2, 3].includes(1);
Is this by design or am I missing a setting in tsconfig.json?
Clarification on functionality of Typescript's target and lib settings
The simplified way I think about it is that target says what syntax the output JavaScript will have, and lib says what API members your TypeScript source code can use. There is more detail in the answers to these two questions:
Typescript- What is target in tsconfig?
What does the tsconfig option "lib" do?
...it doesn't seem to convert es2017 constructs to es5... Is this by design or am I missing a setting in tsconfig.json?
You're right. This is by design. TypeScript transpiles to the target syntax; it does not polyfill the API members that are missing from the target. Here is a quote from a core member of the TypeScript team:
I think you're confusing transpilation with auto-polyfilling. TypeScript doesn't automatically polyfill for you like Babel does, but does perform syntactic downleveling (e.g. for arrow functions). If you want to use ES6 runtime prototype methods, I'd simply include an appropriate ES6 polyfill and its accompanying definition file.
If your lib includes API members (such as Array.prototype.include) that aren't present in the target runtime, then you need to install polyfills that provide those API members.
You can see list Browser compatibility for includes method here.
In this, it did not support IE.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
Quite a simple question, but I haven't found an answer yet anywhere: Is there some switch to make TypeScript compile arrow functions into plain JavaScript functions?
I use them a lot in my code, and I don't want to rewrite everything. But I lately realized, that IE doesn't support them.
I already tried to switch the script version to ES5, but then my code won't compile anymore, because I'm using "filter" as well, which doesn't seem to be a part of it. However, I don't know, if that would do the job in the first place.
If you want to just convert your arrow functions to regular functions and keep the rest of the code compiling, you can set the target config to es5 and lib property to es6:
"compilerOptions": {
"target": "es5",
"lib": ["es6"],
// ....
}
This will allow your code that uses ES6 features to compile while targeting ES5. But you have to make sure those features (like filter) are available at runtime. If they are not available it will throw runtime exceptions.
Arrow functions are a part of ES6, but you should be able to transpile your code to ES5 with Babel. There is a babel-preset-typescript which you should be able to use for this.
When I use ES6 features like for example template string, arrow functions, destructuring within a TypeScript file. Afterward I compile the file to normal JavaScript ...
Are the ES6 syntax compiled too by the TypeScript compiler? Or do I have to use an additional compiler (Babel)?
Are the ES6 syntax compiled too by the TypeScript compiler? Or do I have to use an additional compiler (Babel)?
I disagree with the Fylax's answer. The TypeScript compiler doesn't require an additional tool for converting the ES6 syntax to ES 3 or 5.
The TypeScript compiler tranpiles the new syntax (let, for … of, arrow functions, rest parameters, etc.) to ES 3 or 5. But it doesn't provide any polyfill by itself. In order to use a recent API (like Promise) on a old VM ES 3 or 5, you have to:
Load a polyfill (like es6-promise) that makes the API available;
Say the compiler to use the standard typings for this API.
It is a robust design option. With typeScript, you have to choose carefully the polyfills you need, and to test them on the different browsers you target.
By default, when the target is ES 3 or ES 5, the compiler doesn't use the definitions for the recent ECMAScript API. See the documentation:
Note: If --lib is not specified a default library is injected. The default library injected is:
► For --target ES5: dom,es5,scripthost
If a polyfill makes an API available, then we can configure the compiler to use it. Here is an example of configuration file tsconfig.json for using promises on ES5 VM:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "es5", "es2015.promise"]
}
}
However, Babel can convert a few more features to ES 5 than TypeScript does. See the compatibility table from Kangax.
You need additional compilers that downport your code from ES6 to ES5.
TypeScript is pretty smart and will do most of the work for you (i.e. translate let to var or arrow functions to standard functions with right scope and bindings).
EDIT: as #Paleo pointed out, on 99% you don't need any external compiler as you can provide to TypeScript an extra library (polyfill) which makes everything work fine.
You will need an extra compiler on very rare cases when you are not covered neither by transpiler nor by polyfill's.