I use a file of exported constant as preprocessor directives. Now I would like to completely strip the body of a constructor if this directive is set. For example:
const MY_PREPROCESSOR_DIRECTIVE = true;
class Foo {
someProp: string;
constructor() {
if (MY_PREPROCESSOR_DIRECTIVE) return;
this.someProp = "string";
}
}
This works nicely, I'm using closure compiler for building, which strips the body and the whole thing is turned into only just:
class Foo {}
In TypeScript this causes errors though, because someProp
and otherProp
are "not definitely assigned in the constructor". And in JavaScript files, I don't get errors in the constructor, but all properties are getting set to string | undefined
:
const MY_PREPROCESSOR_DIRECTIVE = false;
class Foo {
constructor() {
if (MY_PREPROCESSOR_DIRECTIVE) return;
this.someProp = "string";
}
myMethod() {
if (MY_PREPROCESSOR_DIRECTIVE) return;
this.someProp.substring(0);
// ^^^^^^^^^^^^^------ Object is possibly 'undefined'.
}
}
(js playground | ts playground)
Is there a way to let TypeScript know the directive will always be false so that it doesn't mark this.someProp
as string | undefined
? I tried to mark the return
statement of the constructor as unreachable code, but I'm not sure how to achieve this.
CodePudding user response:
Since TS know nothing bout runtime
MY_PREPROCESSOR_DIRECTIVE` I think it worth using strategy pattern.
SO, you have two variants of Foo
class. One with MY_PREPROCESSOR_DIRECTIVE = true
and another one with MY_PREPROCESSOR_DIRECTIVE = false
.
const MY_PREPROCESSOR_DIRECTIVE = false;
interface Foo {
someProp?: string;
myMethod: () => void
}
class WithDirective implements Foo {
someProp: string;
constructor() {
this.someProp = "string";
}
myMethod() {
this.someProp.substring(0)
}
}
class WithoutDirective implements Foo {
someProp: undefined
constructor() { }
myMethod() { }
}
const Foo = MY_PREPROCESSOR_DIRECTIVE ? WithDirective : WithoutDirective
If you don't like the fact that there will be always 1 unused class in a build, you can use class expression:
const MY_PREPROCESSOR_DIRECTIVE = false;
interface Foo {
someProp?: string;
myMethod: () => void
}
const Foo =
MY_PREPROCESSOR_DIRECTIVE
? class implements Foo {
someProp: string;
constructor() {
this.someProp = "string";
}
myMethod() {
this.someProp.substring(0)
}
}
: class implements Foo {
someProp: undefined
constructor() { }
myMethod() { }
}
Personally, I think first example is more readable and easier to maintain. Also, I don't think that it will have a significant impact on your bundle size.
CodePudding user response:
It seems one way to work around this issue, in JavaScript anyway, is to explicitly set the type using JSDoc:
constructor() {
if (MY_PREPROCESSOR_DIRECTIVE) return;
/** @type {string} */
this.someProp = "string";
}
I'm not sure if there's a similar way to do this with TypeScript, so this might very well be unintentional behaviour.