I'm experiencing issues with duplicated Typescript type declarations in the following case:
I've got the following dependency tree for my application A:
A->#angular/http:2.3.1
A->B->#angular/http:2.3.1
Both A and B are managed by npm. After running
npm install
the filesystem looks like this:
A/node_modules/
#angular/http
...
B/node_modules
#angular/http
The problem seems to be that now there are two type declarations of #angular/http types like Response, or Headers. And somehow the Typescript transpiler seems unable to handle that - resulting in the following error message:
TS2453:The type argument for type parameter 'T' cannot be inferred
from the usage. Consider specifying the type arguments explicitly.
Type argument candidate 'Response' is not a valid type argument
because it is not a supertype of candidate 'Response'. Types of
property 'headers' are incompatible. Type 'Headers' is not assignable
to type 'Headers'. Types have separate declarations of a private
property 'mayBeSetNormalizedName'.
Reading the message, I guess this is a hickup of Typescript not being able to match the duplicated type declarations.
Anybody experienced the same issue? How to handle that problem? How to handle such name collisions?
Meanwhile I found out that you can fix this error by explicitly importing the according types inside the using class of A. In my case (cp. error message above), I needed to:
import {Response, Headers} from '#angular/http';
I had the same problem. There are basically two ways to solve this.
Make a UMD module of project B. This might take a lot of time
use as any as TheRequiredObject see below.
Let assume you got this class in project b:
export class B{
getSome(): Observable {
return this.http.get('some_url');
}
}
and this is what you want in project a:
export class A{
getSomeFromB: Observable{
return B.getSome() as any as Observable;
}
}
Related
Using Vue with Typescript makes it possible to specify types for props: Strongly typing props of vue components using composition api and typescript typing system
However, as far as I can see these props are only checked at runtime. Here's a simple example component. It has two props a, and b. One is a number and the other a string. Additionally b has a validator, it is only valid if it's equal to hi
<template>
<p>{{a}}</p>
</template>
<script>
import {defineComponent} from 'vue';
export default defineComponent(
{
name: "TestComp",
props:{
a:number,
b:{
type: String,
validator:(val)=>val=="hi"
}
}
}
)
</script>
Here's how you could (incorrectly) use the component. The prop a has a wrong type and the prop b fails its validation:
<TestComp a="hi" b="ho"/>
Is it possible to find this bug at compile time?
Out of the box, Vue only complains at runtime, with a message such as Invalid prop: custom validator check failed for prop "b".
After searching online, I found that prop type validation can be switched on as an experimental Vetur feature. But this doesn't call the custom validator, so in the example above, Vetur can't find the problem for b.
Also, I'm not always using Vetur and would prefer to have the wrong types trigger an actual compile time error.
For (a): Type definitions
The Typescript / Vue Template compiler does not check for this.
Why? Because the type information is not available to the template compiler.
It is possible to analyze this outside the "normal" template compiler. WebStorm has a check for this.
For (b): Validator
The validation is done at runtime and cannot be checked at compile time.
You can write many of those assertions by adding the exact type to the prop, then WebStorm can check them. When using type checks, it is best use typescript (although you can use /** #type */ comments, too)
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent(
{
name: "TestComp",
props:{
b:{
type: String as PropType<"hi">,
validator:(val)=>val==="hi"
}
}
}
)
</script>
Try vue-tsc. It's a part of Vite vue-ts preset that allows to check Vue templates on compile. I was going to use Vite along with Webpack just for templates type checking, but it seems like vue-tsc can do it by itself. However Vite CLI makes configuration easier as it provides all the base TS config, so we only need to compare with already existed tsconfig and take what we need.
I'm pretty new to TypeScript and trying to make automated tests where I dependency-inject IndexedDB into my code, but the library I'm using to mock IDB doesn't have a .d.ts, so I tried to roll my own simple declaration file to get around this, which looked something like this:
declare let fakeIndexedDB: IDBFactory;
declare module 'fake-indexeddb' {
export var fakeIndexedDB;
}
However, when I tried using this type, I got the error:
type 'typeof import("fake-indexeddb")' is missing the following properties from type 'IDBFactory': cmp, deleteDatabase, open
Mousing over in VSCode, it looks like the type of IDBFactory is this type from lib.dom.d.ts:
declare var IDBFactory: {
prototype: IDBFactory;
new(): IDBFactory;
};
But what I wanted to import was the interface type directly above it. How would I say in my declaration file that I want to reference the interface in lib.dom.ts, not the var that uses it? I can see that jsdom was able to make a class in their .d.ts that references DOM types with both an interface and var, but they also don't use "declare module".
Problem
The problem with this code:
declare let fakeIndexedDB: IDBFactory;
declare module 'fake-indexeddb' {
export var fakeIndexedDB;
}
is that the type of the exported fakeIndexedDB is any. It's a different variable than the one declared above it. Basically, fake-indexeddb defined like that is a module that exports a single variable called fakeIndexedDB of unspecified type.
Solution
What you should do instead is this:
declare module 'fake-indexeddb' {
const fakeIndexedDB: IDBFactory;
export = fakeIndexedDB;
}
Whether to use export =, export or export default depends on how the actual JavaScript library is built. The above syntax is recommended if fake-indexeddb exports a single member and it's meant to work well when imported using the require function. See if it works, and if not, consult the source code.
Flow allows you to use the following syntax to import types:
// SomeClass.js
export default class SomeClass {}
// SomeFile.js
import type SomeClass from './SomeClass';
What's the benefit of using import type instead of import? Does it tell Flow more information and let it perform better static analysis?
For the specific case of classes, it is either example will work. The key thing is that it breaks down like this:
import type ... from imports a Flow type
import ... from imports a standard JS value, and the type of that value.
A JS class produces a value, but Flowtype also interprets a class declaration as a type declaration, so it is both.
So where is import type important?
If the thing you're importing doesn't have a value, using a value import will in some cases be interpreted as an error, because most JS tooling doesn't know that Flow exists.
export type Foo = { prop: number }; for instance can only be imported with import type { Foo } from ..., since there is no value named Foo
If the thing you're importing has a JS value, but all you want is the type
Importing only the type can make code more readable, because it is clear from the imports that only the type is used, so nothing in the file could for instance, create a new instance of that class.
Sometimes importing only the type will allow you to avoid dependency cycles in your files. Depending on how code is written, it can sometimes matter what order things are imported in. Since import type ... only influences typechecking, and not runtime behavior, you can import a type without actually requiring the imported file to execute, avoiding potential cycles.
As stated at this link
With import type, you want to import the type of the class and not really the class itself.
An example given at the same link as below
// #flow
// Post-transformation lint error: Unused variable 'URI'
import URI from "URI";
// But if you delete the require you get a Flow error:
// identifier URI - Unknown global name
module.exports = function(x: URI): URI {
return x;
}
Due to we imported URI into this component, linter will check if we have used the class in this component. However, we only using it as a flow type checking and hence linter will throwing an error saying we imported unused variable.
I'm getting the following error:
src/models/priceAdjustment.ts(55,2): error TS2345: Argument of type '{ as: string; foreignKey: string; gql: any; }' is not assignable to parameter of type 'AssociationOptionsHasMany'.
Object literal may only specify known properties, and 'gql' does not exist in type 'AssociationOptionsHasMany'.
The error is obvious, I'm trying to assign a "gql" key which doesn't exist (I'm attaching it on so I can access it later on in my code somewhere else).
What I'd like to do, is extend the model.hasMany interface here, and add my own key. Is this possible? If so, how do I?
My current workaround is to cast the object to <any> before passing it in, which feels like a dirty workaround.
I have attempted the following:
import Sequelize from 'sequelize';
export declare module Sequelize {
export interface MyAssociationOptionsHasMany extends Sequelize.AssociationOptionsHasMany {
gql?: any
}
export interface MyAssociations extends Sequelize.Associations {
hasMany(target: Sequelize.Model<any, any>, options?: MyAssociationOptionsHasMany): void;
}
}
And now I get the following error:
TypeError: Cannot read property '239' of undefined
at getDeclarationSpaces (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:33121:54)
at checkExportsOnMergedDeclarations (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:33067:41)
at checkModuleDeclaration (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35036:17)
at checkSourceElement (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35505:28)
at Object.forEach (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:275:30)
at checkSourceFileWorker (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35566:20)
at checkSourceFile (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35551:13)
at getDiagnosticsWorker (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35604:17)
at Object.getDiagnostics (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35593:24)
at C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:56188:85
You can do this via Declaration Merging. The best way I have found this to work is by importing the library into an "extension" file and extending the library from there. For instance,
extensions/library-name.extension.ts:
import AssociationOptionsHasMany from '/path/to/definition';
import 'library-name';
declare module 'library-name' {
interface InterfaceToExtend {
hasMany(options: AssociationOptionsHasMany): ReturnType;
}
}
In the above example, we want to extend an Interface created in the library-name module. Since we are using imports, TypeScript automatically creates a module for the file, so we have to tell TypeScript that we want to extend the interface in the library-name module, not the module created for this file. To do that, we simply declare the module using the same name as the library we imported.
After telling TypeScript in which module the interface should be extended, we just use Declaration Merging by defining an interface with the same name as the interface that should be extended and adding whatever we need to it. In this case, that would be a new method that takes as an argument the specific type we want to pass it.
I have also had to update the tsconfig.json file so the extensions would be loaded first. You can do that by including the extensions directory before the rest of the app:
{
/* Blah, settings, blah */
includes: ['./app/extensions/*.ts', './app/**/*.ts'],
/* Blah, settings, blah */
}
If you're using Sublime, it might complain still, but actually compiling the code or exiting out of Sublime and reopening it seems to work just fine.
A word of warning. Declaration Merging is powerful, but can result in unexpected behavior. In general, try to avoid extending components you (or someone else) have defined elsewhere. If may be a sign that you are using these components incorrectly or the need for refactoring. Remember, the benefit of TypeScript is strict(er) typing.
I have a file types.js that contains the folliwing:
// #flow
export type PropTypes = {
fields: Object
};
I want to make the fields type constraint more specific. fields corresponds to a property added by a library called redux-form.
redux-form has a corresponding file under flow-typed/npm/redux-form_v5.x.x.js (side question: how does this get added to the filesystem here?).
I presume this contains the type definitions for the library.
How can I bring in the relevant type definition from this into my file types.js and apply it to the fields property?
I think the type definition should look like this:
{ [fieldName: string]: InputProps }
I have tried the following to bring in InputProps:
adding flow-typed/npm/redux-form_v5.x.x.js to the .flowconfig file
...but running yarn flow check says:
identifier InputProps. Could not resolve name
I added the following and it solved my problem:
import type { InputProps } from 'redux-form'