When TypeScript code is compiled using tsc
, it undergoes both type checking and transpilation to JavaScript, with these processes being mostly independent of each other.
The main purpose of type checking is to alert the developer to potential issues that could cause problems at runtime. It's important to note that these alerts are just warnings; they don't actually prevent runtime problems. For example, when attempting to assign to a readonly
property, a warning is generated:
Cannot assign to 'doNoTChange' because it is a read-only property.(2540)
This warning signals that there might be an issue that needs to be addressed, but the compiler itself does not fix the problem (which might require more advanced logic that a compiler cannot provide) by generating code without the problem.
The transpilation process involves converting TypeScript code to JavaScript by removing static type system features and generating runtime code based on the target version of JavaScript specified in the compiler options. This means that features like readonly
, which exist purely in the type system, do not appear in the resulting JavaScript code. The compiler can still produce JavaScript even if the type checker identifies errors, as this is an intentional design choice.
If you wish for the compiler to not output JavaScript in case of errors, you can utilize the --noEmitOnError
compiler option. If you encounter issues with this option, it's advisable to carefully review your compiler configuration and consider creating a reproducible example to help others identify the problem.
Delving deeper, the challenges you're facing may relate to type erasure in general. In TypeScript, it's beneficial to think about the desired runtime behavior first and then use TypeScript to provide stronger types for guidance during development. JavaScript, at runtime, will attempt to execute x.toUpperCase()
regardless of the type of x
. By defining let x: string;
and later assigning x = 15; x.toUpperCase()
, the compiler will warn you when you assign
x = 15</code, indicating a potential issue.</p>
<p>To achieve the desired runtime behavior, consider using JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters" rel="nofollow noreferrer">getters</a> for properties like <code>doNoTChange
. By creating a getter without a setter, attempting to set the property will result in a runtime error. A sample implementation could be:
class readOnlyClass {
private _val: string;
get doNoTChange(): string {
return this._val;
}
constructor(ss: string) {
this._val = ss;
}
}
When targeting ES2017
, the generated code would be:
class readOnlyClass {
constructor(ss) {
this._val = ss;
}
get doNoTChange() {
return this._val;
}
}
Resulting in the following behavior:
let test = new readOnlyClass('aaa');
console.log(test.doNoTChange); // outputs 'aaa'
test.doNoTChange = 'after change'; // compilation error, and at runtime:
// TypeError: setting getter-only property "doNoTChange"
console.log(test.doNoTChange);
This setup leads to a compiler error occurring alongside a runtime error.
Hopefully, this guidance sheds light on your situation. Best of luck!
Link to code