EcmaScript 6, TypeScript and JSX
EcmaScript 6
Tabris.js 2 supports all ES5 and most ES6/ES7 (aka ES2015/ES2016) features without transpilers like Babel. This includes:
- Arrow functions
- Classes
- const
- Default parameters (except iOS 9)
- Destructuring assignment
- Exponentiation operator(except iOS 9)
- for…of
- Generators (except iOS 9)
- Iterators
- let
- Map
- Methods
- Object property shorthands
- Promise
- Reflect (except iOS 9)
- Rest parameters (except iOS 9)
- Proxy (except iOS 9)
- set and get literals
- Set
- Spread operator
- Symbol
- Template literals
- Typed Arrays and ArrayBuffer
- WeakMap
- WeakSet
NOT supported (without transpiling) are:
- async/await: Use
then
instead. - import/export: Use the
require
function andexports
object instead.
TypeScript
Tabris.js 2 is optimized for use with TypeScript 2. TypeScript is a type-safe dialect of JavaScript/EcmaScript and also supports ES6 module syntax (import
and export
statements) and async
/await
. A complete guide to TypeScript can be found at typescriptlang.org. As an IDE we can recommend Visual Studio Code with the tslint extension, but there are many suitable TypeScript IDEs out there.
TypeScript files have to be “transpiled” to JavaScript before execution. The compiler is included when generating a new Tabris.js app using the tabris init
command, so no additional installation step is required. Simply choose TypeScript App
when given the option. After the app has been generated, type npm start
to serve the TypeScript code to your Tabris Developer App as JavaScript. In Visual Studio Code you can also use the preconfigured start
task instead.
As long as the task is still running, changes to your TypeScript code (any .ts
file in src
) will be detected by the TypeScript compiler automatically. No restart is needed.
Stay safe
In TypeScript not all APIs, not even all Tabris.js APIs, are perfectly type safe. It’s therefore recommended to follow these general guidelines:
Casting: Ideally, perform instanceof
checks before casting. In TypeScript, castings can fail silently.
Implicit “any”: In TypeScript a value of the type any
is essentially the same as a JavaScript value. The compiler will accept any actions on this value, including assigning it to typed variables. An implicit any
may occur if
you do not give a type for a variable, field or parameter, and none can be inferred by assignment. Always give the type of function parameters. Fields and variables are safe only if they are assigned a value on declaration.
Widget property access: Do not use widget.set(key, value)
or widget.get(key)
. Instead access properties directly like this: widget.key = value
. You can also safely use property objects: like widget.set({key: value})
and new Widget({key: value})
.
Widget event handling: Do not use widget.on(event, handler)
. Instead, use widget.on({event: handler})
.
Widget apply method: Use widget.apply
only to set properties of the base Widget
class, like layoutData
.
Selector API and WidgetCollection: By default the widget methods find
, children
, sibling
return a “mixed” WidgetCollection. This means you would have to do a type check and cast to safely retrieve a widget from the collection. However, you can also use widget classes (constructors) as selectors, which results in a collection that TypeScript “knows” to only have instances of that type. In that case no cast will be necessary. Example: widget.children(Button).first('.myButton')
returns a button (or null), but nothing else. It should be noted that the set
method of such a WidgetCollection is still not type-aware. You can use the forEach
method instead to safely set properties for all widgets in the collection.
NPM modules: The tabris
module is automatically type-safe, but the same is not true for all modules that can be installed via npm
. You may have to manually install declaration files for each installed npm module.
JSX
JSX is an extension to the JavaScript/TypeScript syntax that allows mixing code with XML-like declarations. Tabris 2 supports type-safe JSX out of the box with any TypeScript based project. All you have to do is name your files .tsx
instead of .ts
. You can then use JSX expressions to create widgets. For example…
ui.contentView.append(
<composite left={0} top={0} right={0} bottom={0}>
<button centerX={0} top={100} text='Show Message'/>
<textView centerX={0} top='prev() 50' font='24px'/>
</composite>
);
…is the same as…
ui.contentView.append(
new Composite({left: 0, top: 0, right: 0, bottom: 0}).append(
new Button({centerX: 0, top: 100, text: 'Show Message'}),
new TextView({centerX: 0, top: 'prev() 50', font: '24px'})
)
);
JSX in Tabris.js TypeScript apps follows these specific rules:
- Every JSX element is a constructor call. If nested directly in code, they need to be separated from each other (see below).
- Attributes can be either strings (using single or double quotation marks) or JavaScript/TypeScript expressions (using curly braces).
- Each elements may have any number of children, all of which are appended to their parent in the given order.
- Element names starting lowercase are intrinsic elements. These include all instantiable widget build into Tabris.js, as well as
WidgetCollection
. The types of these elements don’t need to be explicitly imported. - Element names starting with uppercase are user defined elements, i.e. any class extending a Tabris.js widget. These do need to be imported.
- To support attribute checks on user defined elements, add a field on your custom widget called
jsxProperties
. The type must match the type of the first constructor parameter. You do not have to assign anything to the field. - While the JSX expressions themselves are type-safe, their return type is not (it’s
any
), so follow the instructions for casting above. It can be considered safe to use unchecked JSX expressions withinwidget.append()
, as all JSX elements are appendable types.
Note that this is not valid:
ui.contentView.append(
<button centerX={0} top={100} text='Show Message'/>
<textView centerX={0} top='prev() 50' font='24px'/>
);
JSX elements that are nested directly in code must be separated like any expression, in this case by a comma:
ui.contentView.append(
<button centerX={0} top={100} text='Show Message'/>,
<textView centerX={0} top='prev() 50' font='24px'/>
);
To avoid this, you may wrap your widgets in a WidgetCollection
. This example has the same effect as the previous:
ui.contentView.append(
<widgetCollection>
<button centerX={0} top={100} text='Show Message'/>
<textView centerX={0} top='prev() 50' font='24px'/>
</widgetCollection>
);
JSX without TypeScript
If you want to use JSX without writing TypeScript, you can still use the TypeScript compiler to convert your .jsx
files to .js
. Simply generate a TypeScript app and add an entry "allowJs": true
in the compilerOptions
object of tsconfig.json
. Then change the filenames in the include
object from .ts
and .tsx
to .js
and .jsx
. You may also have to adjust your linter setup, if you use any.