Why We Chose TypeScript For Cocos Creator 3.0

This post was originally posted on our Chinese forum by our Senior Technical Director, Jare Guo. Learn more about him in our interview.


Although Creator has long supported TypeScript, it was not until v3.0 that support for JavaScript was officially abandoned. Many developers are now learning and using TypeScript for the first time and encountering obstacles.

In order to make everyone’s upgrade smoother, this tutorial will briefly review this adjustment and answer some questions that developers are likely to encounter during the language upgrade process.

Our betting on TypeScript

Our support for TypeScript has a long history, dating back to v1.5.0, released in May 2017. Since then, we have been listening to feedback from TypeScript developers and gradually improving TypeScript support.

In 2018, the 3D project team began to use TypeScript to rewrite the entire 3D engine and editor, verifying TypeScript’s ability to handle super-large-scale projects and giving us greater confidence in TypeScript Experience.

By 2019, when Cocos Creator 3D was officially released, we officially abandoned JavaScript support for the first time and then continued to listen to developers’ feedback on this change.

Which language to use as the development language of the engine is not an easy decision, and it has a huge impact on developers and the ecology. When Cocos Creator originally chose JavaScript, it has already experienced a lot of criticism. If it weren’t for the previous years on TypeScript, we would not have the courage to make this key adjustment in 3.0.

Why switch to TypeScript

At the beginning of the project in 2014, TypeScript became a good bridge for us to get started with JavaScript due to the familiar syntax. However, since TypeScript had just been officially released at that time and the ecosystem was still immature, we could only use native JavaScript first.

After so many years of development, today TypeScript has become a very mature language, with a rich ecology and large base, and it can meet the needs of any type of game development!

From my contact with the R&D team in the past few years, most teams can’t escape the true essence of TypeScript. There are many articles on TypeScript on the Internet sharing the advantages of TypeScript. This is our perspective on the language:

  • We always believe that a good programming language is the cornerstone of project success. It is true that any language and framework are just tools, and good programmers can do a good job no matter what tools they use. But a good language can reduce the overall project risk, make developers happy, improve collaboration efficiency, and reduce the R&D costs of the entire team in the long run.
  • We always believe that Cocos Creator wouldn’t be just a small game engine, so it must be able to support the development of large and heavy games. JavaScript may be able to meet the needs of masters to do some small projects by themselves and may be able to make small things for fun quickly, but it does not meet the industry standards of large projects.
  • We always believe that JavaScript, as a weakly-typed language, will sooner or later become the optimization bottleneck of the Cocos Creator project. Only a strongly-typed language has the opportunity to improve the performance of the entire project completely. Therefore, we continue to explore the possibility of compiling JavaScript into a native language, which is inseparable from the support of TypeScript. Today there are mature projects like AssemblyScript that support the compilation of TypeScript dialects into WebAssembly. In fact, there are many similar projects. Only by unifying everyone’s development language from the perspective of ecology and community will we have the opportunity to give you this gift in the future.
  • We always believe that the fragmentation of language will bring about the fragmentation of ecology. Today, there are a bunch of TypeScript fans in the Cocos Creator community. Many high-quality tutorials, plug-ins, and posts are published using TypeScript. If the official documents and official cases are written in one language, it will not be conducive to the learning of another language. If the community has two languages ​​for a long time, it is even more difficult for everyone to reuse the results of previous work. JavaScript developers cannot adapt to the maintenance of TypeScript projects, and TypeScript developers will also encounter obstacles when reusing the original JavaScript components.

The following content is a collection of questions and tips by our engine quality supervision director from the forum feedback

Common TypeScript Cognitive Mistakes

Misunderstanding: Cocos Creator only supports TypeScript, not JavaScript

TypeScript is a superset of JavaScript, and TypeScript closely depends on JavaScript. Cocos Creator 3.0 still supports the use of TypeScript and JavaScript.

However, Cocos Creator encourages users to use TypeScript to get a better development experience and improve development quality. The editor, only supports the creation of TypeScript scripts.

If you really want to use JavaScript, creating JavaScript files in other ways (Explorer, Finder, etc.) is still allowed.

Misunderstanding: import/export is exclusive to TypeScript, I cannot use require in JavaScript

import/export is the syntax for module support introduced by the JavaScript specification ECMAScript 2015, so it can be used in JavaScript and is not exclusive to TypeScript.

On the other hand, require/exports/module is a variable in the CommonJavaScript module system. They are not part of the JavaScript standard.

Although, limited support for CommonJavaScript modules is provided in 3.0.

Migrate JavaScript code to TypeScript

As mentioned above, JavaScript can be used directly. But as long as you understand the usage of TypeScript, it is easy to migrate JavaScript code to TypeScript.

The simplest migration: change the suffix

You can directly rename the .js file to .ts to complete the simplest migration-as long as JavaScript is okay at runtime, it must be okay to change to TypeScript.

After renaming, there will be a lot of errors in the IDE because of the lack of type information. If you don’t want to solve these types of problems, for the time being, you can add a comment to the head of the entire file:

// @ts-nocheck

To skip the type check of the file.

Add type information in the code

For relatively simple JavaScript code, just add some type information. e.g.:

function fn(a, b, c) {

// ...

}

Specifying the parameter type for fn is OK:

function fn(a: string, b: number, c: boolean) {

// ...

}

TypeScript will automatically infer the return value of the function based on the code in the function body. Of course, you can also specify explicitly:

function fn(a: string, b: number, c: boolean): string {

// ...

}

Declare type information outside the code

Sometimes, JavaScript code is not written by ourselves, but library code is provided by a third party. At this time, we can add type information for it without editing it.

For example, there is a third-party JavaScript file foo.js:

module.exports = function foo (a, b, c) {

// ...

}

Create a foo.d.ts with the same name but an extension .d.ts in the same directory:

// Just declare the signature of foo, the function body does not need

function foo(a: string, b: number, c: boolean): string;

export default foo;

Strict mode: “null” problem

Switch from JavaScript to TypeScript may cause confusion with some type problems. Consider the following:

class C {

material: Material = null;

}

This code will report an error in the IDE, and the source of the error is the declaration of the attribute material.

There is a situation where the material property is only null when it is initialized, but it has a value for any subsequent access. For example, when attaching the @property decorator to the property, you can edit the field in the editor. After dragging, the Creator will assign the value for us.

How do we convey this information to TypeScript?

Let’s analyze the reason for the error. material: Material declares the material field as the Material type, which means that it is of the Material type any time you get the material.

The initializer = null tells it to initialize the material to the null value null, which is contrary to the previous statement.

This is TypeScript’s strict type checking. It requires you to match the type. TypeScript editor turns on this option by default, and Creator is no exception.

Now that we know the cause of the error, we can have the following ideas to solve it.

  1. Correctly describe its type as: either Material or null
  2. Expressed to the TypeScript type system: I just initialized to null, and I actually use Material
  3. Turn off TypeScript’s check for null values.

Nullable type

We can describe the type of material as either Material or null:

material: Material | null = null;

So it is logical to initialize to null.

However, the disadvantage of this is that when you subsequently access material, TypeScript requires you to handle null values. Example:

console.log(this.material.name);

The TypeScript type system will prompt you: this.material may be the null value null, and the properties of null cannot be accessed.

However, as long as TypeScript knows here that it must be non-empty, it will put this error away:

if (this.material) {

console.log(this.material.name);

}

Because you made a judgment when you run into the if statement block, this.material must not be empty so that you can access the name attribute.

Expression is not an empty assertion

It is a bit cumbersome to use the if statement every time you use it, and the if statement will be executed at runtime.

If you can ensure that “this.material must be non-empty when it runs to here”, then we can use the TypeScript non-empty assertion syntax to express:

console.log(this.material!.name);

The exclamation mark ! is called the non-empty assertion operator, which asserts that the previous expression is not empty.

Note that this exclamation mark is unique to TypeScript and is for type purposes only; it will be removed directly after compilation.

We can also make this assertion during initialization:

material: Material = null!; // Equivalent to casting `null` to `Material` type

Another place that is often used for non-empty assertions is Node.getComponent(), this method returns components that may be empty. If you can ensure that the component must exist, you can avoid if judgments through non-empty assertions.

Explicit assignment assertion

In standard JavaScript grammar, it is not necessary to give the initialization type. Such a field will be initialized to undefined:

class C {

@property(Material)

material; // undefined

}

This is also allowed in TypeScript, but you have to prompt TypeScript to make it skip the type check here:

material!: Material;

The exclamation mark after the attribute name is called Definite Assignment Assertion,It tells TypeScript that this field is initialized elsewhere. A field that is declared as Material but initialized as undefined.

Disable null checking

Of course, if you really don’t like TypeScript’s type system, and you prefer to ensure everything by yourself, then you can turn off strict type checking. In project directory/tsconfig.json, add options:

"extends": "./temp/tsconfig.cocos.json",

"compilerOptions": {

"strictNullChecks": false // no

}

It should be reminded that we do not encourage this practice because strict null value checking can reduce some low-level errors when JavaScript code is running.

Conclusion

If you still have questions about the use of TypeScript, you can reply to this post in our forums, and you are welcome to forward this article to those in need!

Pin It on Pinterest