TypeScript Version: 2.1.1
# Code #
{
"compilerOptions": {
"target": "es5",
"lib": [
"es2016",
"dom"
],
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"experimentalDecorators": true,
"noLib": false,
"jsx": "react",
"outDir": "dist"
},
"exclude": [
"node_modules",
"static"
]
}
export class Ajax {
static async getJson<TArray>(url: string) {
const request = new XMLHttpRequest();
return new Promise<TArray[]>(resolve => {
request.onreadystatechange = () => {
if (request.readyState != XMLHttpRequest.DONE || request.status != 200) return;
const data = JSON.parse(request.responseText);
resolve(data);
};
request.open("GET", url, true);
request.send();
});
}
}
import { Ajax } from './utilities/ajax';
import { Holiday } from './models/Holiday';
import { Country } from './models/Country';
import { NationList } from './components/NationList';
import { AppState } from './models/AppState';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { HolidayList } from "./components/HolidayList";
initialize();
async function initialize() {
const countries = await Ajax.getJson<{ id: string, name: string }>('src/data/countries.json');
const holidays = await Ajax.getJson<{ id: number, countryId: string, date: Date, day: string, name: string }>('src/data/holidays.json');
const appState = new AppState(countries, holidays);
ReactDOM.render(
<div>
<NationList appState={appState} />
<HolidayList appState={appState} />
</div>,
document.getElementById("root")
);
}
html<html>
<head>
<title>Scheduling Assistant</title>
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.5/bluebird.min.js"></script>-->
</head>
<body>
<div id="root"></div>
<script src="/static/bundle.js"></script>
</body>
</html>
Promise
is replaced with callbacks, etc. to be compatible with ES5.SCRIPT5009: 'Promise' is undefined
.The runtime has to support Promise
or you must provide a polyfill. See the explanation
Note: first, we need to make sure our run-time has an ECMAScript-compliant Promise available globally. That might involve grabbing a polyfill for Promise, or relying on one that you might have in the run-time that you're targeting.
@gcnew, Thanks for the quick response. I had read that note in the docs, but I didn't really "get it" at the time. Now that I've experienced it, it makes sense.
Anyway, it's a little confusing to me what exactly it means when I put "target": "es5"
in compilerOptions
. I used to think it meant that typescript would generate code that works in an ES5 browser, period. Now, it seems that there are occasional examples where this isn't the case, and you basically have to carefully read the docs to find those out (or infer it from browser errors).
An interesting counter-example to Promise
not working, BTW, is that Symbol
does seem to get polyfilled (at least, I get no error in IE 11 when putting code containing Symbol()
in the same .ts file as I put Promise()
).
So, I'm not seeing a clear rule of thumb on when the typescript compiler will polyfill something and when it won't. I think it would be very helpful to developers if all ES6 language features and types were polyfilled when specifying "target": "es5"
. The way it works now is a violation of the principle of least astonishment IMO.
It is possible to emit perfectly fine es5
(or even es3
) code for async/await that will work as long as the environment provides a Promise polyfill.
However, it would be nice to have a way of forbidding the compiler from emitting code that may require certain polyfills; maybe throwing a syntax error about forbidden syntax, similar to how TypeScript complains about for of
with non-string/array types in es3
targets.
@Kovensky Doesn't the lib
option and compiler errors already do that? You set the lib
option to what you know exists, add other globals you know exist and the compiler does the rest.
@devuxer Are you sure you environment doesn't support Symbol
or some of your dependencies doesn't bring a shim in? I've tried the new asyncInterator
downstream emit and it didn't polyfill Symbol
at all.
The lib
option is only for typechecking, it doesn't affect codegen.
@Kovensky Exactly. You were asking for something that:
nice to have a way of forbidding the compiler from emitting code that may require certain polyfills
That's the compilers goal. It would error if you didn't include the Promise
types. So if you want to support only ES5 environments, you should set lib to es5
and use the polyfills to "upgrade" the environment.
Note that not everything is polyfillable; you may also be working with a partial implementation. For ES5, say, Array.prototype.every
is easily polyfillable, but Object.defineProperty
is impossible except in the specific case of enumerable+writable+configurable value properties. For ES6, Object.assign
is also easily polyfillable, but good luck doing anything about Proxy
.
Though I guess that, in this case, it could work to have the compiler refuse to accept async
if it can't generate the return type for the function. If you don't include at least es2015.promise
, it's impossible to annotate the function's return type, as you can't use Promise<T>
, but the compiler requires that it be a Promise<T>
.
One idea is when hook is available, a tool may be created alongside tsc to install the polyfills you need for the specific target. 馃尫
Note that not everything is polyfillable; you may also be working with a partial implementation.
Absolutely. I think in the ideal world the type information would possibly block the impossible things, but I also realise it's pretty improbable to build that out since most people will pick up the default lib
anyway. Luckily that's basically the point though - no type info = compiler error.
Yeah, in the async
case, I believe it's handled already because the compiler will error if the Promise
value and type does not exist.
One idea is when hook is available, a tool may be created alongside tsc to install the polyfills you need for the specific target.
That could be a nice idea. Combine that with browser statistics information should be pretty easy too so that only the polyfills on each supported environment are included.
Well, the issue has been closed, but I haven't really found the answer to the question here :(
@Maximaximum,
The answer for me has been to visit polyfill.io anytime I get a runtime error in IE11. The error almost always gives a hint as to what feature is missing, and polyfill.io almost always has a polyfill for it.
@devuxer Thanks for the summary, it's really helpful!
When jsconfig.json target is 'es5' and lib has 'es6' following should 'fix' all IE11 polyfills:
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6"></script>
@nsteenbeek Thanks for the solution. Helped me out.
I need solution where I have no access to polyfill.io (no internet access)
@xdvarpunen try babel-polyfill
@nsteenbeek If ever meet you in person i'm gonna give you a large hug and buy you a beer, your solution helped me out.
Most helpful comment
@gcnew, Thanks for the quick response. I had read that note in the docs, but I didn't really "get it" at the time. Now that I've experienced it, it makes sense.
Anyway, it's a little confusing to me what exactly it means when I put
"target": "es5"
incompilerOptions
. I used to think it meant that typescript would generate code that works in an ES5 browser, period. Now, it seems that there are occasional examples where this isn't the case, and you basically have to carefully read the docs to find those out (or infer it from browser errors).An interesting counter-example to
Promise
not working, BTW, is thatSymbol
does seem to get polyfilled (at least, I get no error in IE 11 when putting code containingSymbol()
in the same .ts file as I putPromise()
).So, I'm not seeing a clear rule of thumb on when the typescript compiler will polyfill something and when it won't. I think it would be very helpful to developers if all ES6 language features and types were polyfilled when specifying
"target": "es5"
. The way it works now is a violation of the principle of least astonishment IMO.