Context
I'm making a React renderer for NativeScript (i.e. a library that allows you to declare NativeScript UIs using React), and I want to provide typings for it.
React's typings already fully support React DOM (i.e. there's references to HTMLElements throughout), but I need to augment the typings to make them aware of NativeScript elements, too. To provide accurate typings, I need to provide references to external modules, e.g. by importing ActionBar
. My attempt looks something like this (which does compile, but isn't picked up by the other modules in my project):
// react-nativescript/index.d.ts
import { ActionBar } from "tns-core-modules/ui/action-bar/action-bar";
declare namespace JSX {
interface IntrinsicElements {
actionBar: React.ClassAttributes<ActionBar>
& React.NativeScriptAttributes<ActionBar>
}
}
declare namespace React {
interface NativeScriptAttributes<T> {
className?: string;
children?: ReactNode;
onClick?: MouseEventHandler<T>;
}
// Omitting DetailedNativeScriptFactory<P, T> for brevity.
interface ReactNativeScript {
actionBar: DetailedNativeScriptFactory<
NativeScriptAttributes<ActionBar>,
ActionBar
>,
}
}
Root of the problem
Adding the top-level import ActionBar
changes the file from a 'script' to a 'module':
In TypeScript, just as in ECMAScript 2015, any file containing a top-level
import
orexport
is considered a module. Conversely, a file without any top-levelimport
orexport
declarations is treated as a script whose contents are available in the global scope (and therefore to modules as well).
Further clarification of terminology is provided by TypeScript Contributor Mohamed Hegazy.
Upon importing it, the declarations cease to be available to other modules. That is to say, I can only use the type React.ReactNativeScript
when my file lacks top-level imports/exports. Otherwise, the following error appears:
Namespace 'React' has no exported member 'ReactNativeScript'.
NOTE: this may be incorrect configuration on my part of my tsconfig.json
, but I have tried just about every combination of tsconfig typeRoots
and package.json
types
at this point and nothing makes a difference.
(Failing) alternative approach
In order to keep the declarations available to other modules, we can try moving the ActionBar
imports into the namespaces themselves, e.g.:
// react-nativescript/index.d.ts
declare namespace JSX {
import { ActionBar } from "tns-core-modules/ui/action-bar/action-bar";
import { ClassAttributes, NativeScriptAttributes } from "react";
interface IntrinsicElements {
actionBar: ClassAttributes<ActionBar>
& NativeScriptAttributes<ActionBar>
}
}
However, this introduces another compilation error:
Import declarations in a namespace cannot reference a module
... not to mention hundreds of other resulting compiler errors that I'd rather not dig into.
Known issue?
I found this seemingly related issue: https://github.com/Microsoft/TypeScript/issues/4166
Our current answer today is "Move your stuff to another
.d.ts
file". This is an OK workaround, but not great. The real sticker is when there's a type defined inside an external module -- it's impossible to use those types to augment a global interface, even though this is a thing that actually happens. It gets even trickier because this encourages people to move types into the global namespace when they didn't want to in the first place, exacerbating the problem.
I don't really know what is meant by "Move your stuff to another .d.ts
file". I began trying to declare everything I needed in global scope (essentially building a lib.dom.d.ts
for NativeScript), but NativeScript is simply too big to take that approach – and it would be a nightmare if they ever updated their typings.
Question
How can I augment React's typings with references to external modules like tns-core-modules
?
It seems like my initial approach would work if only TypeScript would expose that file to all other files within my library (and any projects that consume it), but no matter my tsconfig.json
settings, the declarations file is only noticed by other modules if it is a script rather than a module.
import
ing use///<reference path="/path/to/types.d.ts">
? That shouldn't turn the script into a module. – Agenesis///<reference types="tns-core-modules/ui/action-bar/action-bar" />
? [Reference.] – Agenesis