Scripting
TypeScript Support

TypeScript support

As well as JavaScript, Meta Spark Studio supports the use of TypeScript.

The TypeScript language is a superset of JavaScript that follows an object-oriented programming structure and provides additional language features such as optional static typing and access modifiers for class members.

When adding a new script in Meta Spark Studio you can choose whether to create a new JavaScript or TypeScript file.

You can use both JavaScript and TypeScript files alongside each other in your effect, without needing to make any changes to existing .js files.


All script files should be given a unique name regardless of the file extension - a TypeScript file and a JavaScript file cannot share a name.


While many IDEs support the TypeScript language, some offer additional features that enhance the development experience. Visual Studio Code for example offers syntax highlighting, automatic code completion and suggestion snippets, among other useful development features.

Listed below are a few of the key language features TypeScript provides.

Module import syntax

The syntax for importing modules from the Meta Spark API is slightly different in TypeScript, but is functionally the same.

// How to load in modules in a TypeScript file
import Scene from 'Scene';
import Diagnostics from 'Diagnostics';
                

Once imported, APIs can be used in the same way as they would be in a JavaScript file.

// How to load in modules in a TypeScript file
import Diagnostics from 'Diagnostics';

// Enable async/await in TypeScript [Part 1]
(async function () {

    // Log a message to the console
    Diagnostics.log("Hello world");
    
// Enable async/await in TypeScript [Part 2]
})();
                

You can also import script packages from the AR Library as you would with JavaScript.

import Cannon from 'cannon';

// Enable async/await in TypeScript [Part 1]
(async function () {
    
    const world = new Cannon.World();
    
// Enable async/await in TypeScript [Part 2]
})();
                

Type annotation

With TypeScript you have the option to explicitly specify the data type of a variable, function parameter, function return type or object property. This differs from weakly typed languages like JavaScript, which infers data types from usage.

The syntax for type annotations is : type, where type is a valid TypeScript type.

// Declaring variables with type annotation in TypeScript
var message: string = "Hello";
var someNumber: number = 10;
var someBoolean: boolean = true;

// Type annotations are optional, so the following declaration is also valid
// The type of 'value' is inferred as a number because of the value assigned
var value = 5;

// Type annotation for a function's parameters and return type
function addTwoNumbers(x: number, y: number): number {
    return x + y;
}
                

Type annotations are used to enforce type checking, which helps to catch common data type errors during compilation.

// Declaring two variables of different data types
var message: string = "Hello";
var someNumber: number = 10;

// Throws a compiler error because we are trying to assign a numerical value to an object of type 'string'
message = 10;

// Throws a compiler error because the function 'toUpperCase' does not exist for objects of type 'number'
someNumber.toUpperCase();
                

Stricter typing can also help with code readability and maintainability.

Type assertion

You can use type assertion to set an object or value's type as a specific Spark type with the as keyword. For example:

// Locate the plane in the scene, which we know will be of type Plane
const plane = await Scene.root.findFirst('plane0') as Plane;

// Create a reference to the position property, which we know will be a PointSignal
const position = plane.transform.position as PointSignal;
                

Doing so allows the compiler to provide correct type information and autocompletion for your objects and values.

Type assertions are compile-time only and won't affect your code's runtime behavior. Read more about type assertion here.

Access modifiers

When creating classes you can set the level of accessibility of a property or method with the three access modifiers that TypeScript provides:

  • public - a public property or method can be accessed from anywhere. If no access modifier is specified, the public modifier is assigned by default.
  • private - a private property or method can only be accessed from within the same class it was declared in. Attempting to access it from outside of the class results in a compilation error.
  • protected - a protected property or method can be accessed from within same class it was declared in, and any subclasses (classes that inherit from the parent class). Attempting to access it from anywhere else results in a compilation error.
class Person {

    // These variables are only accessible from within the 'Person' class
    private firstName: string;
    private lastName: string;

    // This variable is accessible from within the 'Person' class, and any of its subclasses
    protected idNumber: number;
    
    constructor(firstName: string, lastName: string, idNumber: number){
        this.firstName = firstName;
        this.lastName = lastName;
        this.idNumber = idNumber;
    }

    // This function can be called from anywhere, including from outside of the class
    public getInfo(): string {        
        return `Full name: ${this.firstName} ${this.lastName}, ID: ${this.idNumber}`;
    } 
}

// Creating a new instance of 'Person'
const person = new Person("John", "Smith", 101);

// We can call this function here outside of the class because its accessibility is set to 'public'
var personInfo = person.getInfo(); // Result: Full name: John Smith, ID: 101                    
                

Generics

Generics in TypeScript allow you to create reusable components that work with multiple data types rather than just one, which can reduce the amount of code you need to write and maintain.

Consider the following functions which perform the same logic but take parameters of different types:

// Returns the number value that was passed as an argument
function getNumber(someNumber: number): number {
    return someNumber;
}

// Returns the string value that was passed as an argument
function getString(someString: string): string {
    return someString;
}

var firstResult = getNumber(10); // Result: 10
var secondResult = getString("Hello"); // Result: Hello
                

While the above could be made into a generic function with the use of the keyword any, we would lose type-safety as the function would accept any data.

// Returns the value that was passed as an argument
function getValue(someValue: any): any {
    return someValue;
}

var firstResult = getValue(10); // Result: 10

// This wouldn't cause a compilation error since the function accepts any data, but could cause a runtime error if we attempt to perform an incompatible operation
var secondResult = getValue(someOtherObject);
                

With the use of generics, we can create a single function that can take arguments of different data types while remaining type-safe.

These generic type parameters are denoted by the use of <T> or T.

// Returns the value that was passed as an argument
// Works with multiple data types 
function getValue<T>(someValue: T): T{ 
    return someValue;
}

// When calling the function we substitute <T> with the data type of the value we are passing in
var firstResult = getValue<number>(10); // Result: 10
var secondResult = getValue<string>("Hello"); // Result: Hello

// The compiler can also infer the data type of the argument and set the type of 'T' to match, so the following is also valid
var thirdResult = getValue("A string"); // Result: A string
                

Functions can have multiple generic parameters:

// This function has multiple generic parameters and returns the value of the first argument passed
function someFunction<T, U, V>(firstValue: T, secondValue: U, thirdValue: V): T{ 
    return firstValue;
}
                

Classes can also be generic:

// Create a class which 
class GenericValue<T> {
    private value: T;

    constructor(value: T){
        this.value = value;
    }

    public GetValue(): T{
        return this.value;
    }
}

// Create a new 'GenericValue' instance with a number type
var numberObject = new GenericValue<number>(10);
var firstResult = numberValue.GetValue(); // Result: 10

// Create a new 'GenericValue' instance with a string type
var stringObject = new GenericValue<string>("Hello");
var secondResult = stringValue.GetValue(); // Result: Hello
                

TypeScript provides many other language features, which you can learn more about on the TypeScript documentation site.