I'm currently working on a ReactJS project which uses Webpack2 and TypeScript. Everything works perfectly apart from one thing - I can't a find a way to move interfaces that I've written myself into separate files so that they are visible to the whole application.
For prototyping purposes I initially had interfaces defined in files that use them but eventually I started adding some that were needed in multiple classes and that's when all the problems started. No matter what changes I make to my tsconfig.json
and no matter where I put the files my IDE and Webpack both complain about not being able to find names ("Could not find name 'IMyInterface'").
Here's my current tsconfig.json
file:
{
"compilerOptions": {
"baseUrl": "src",
"outDir": "build/dist",
"module": "commonjs",
"target": "es5",
"lib": [
"es6",
"dom"
],
"typeRoots": [
"./node_modules/@types",
"./typings"
],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": false,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true
},
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
],
"types": [
"typePatches"
]
}
As you can see, my tsconfig.json
is in the root of the project directory, all source is in ./src
, I placed my custom .d.ts
files in ./typings
and included it in typeRoots
.
I tested it with TypeScript 2.1.6 and 2.2.0 and neither works.
One way of getting it all to work is to move my typings
directory into src
and then import {IMyInterface} from 'typings/blah'
but that doesn't feel right to me as it's not something I need to use. I want those interfaces to just be 'magically' available throughout my application.
Here's a sample app.d.ts
file:
interface IAppStateProps {}
interface IAppDispatchProps {}
interface IAppProps extends IAppStateProps, IAppDispatchProps {}
Do I need to export
them or maybe declare
? I hope I don't have to wrap them in a namespace?!
Update (October 2020)
Seeing how this question is still surprisingly popular I wanted to explain the solution in more detail.
Firstly, what could and should be confusing to people is that the interface example I gave at the end of my question actually doesn't have any export
keywords even though I'm almost certain I did have them in my files at the time of asking the question. I believe I didn't include them in the question thinking they didn't make any difference, whether they were there or not. Well, it turns out that it's not true and the export
keyword is exactly what makes or breaks you being able to just "use" the interfaces versus having to explicitly import
them.
So, the way it works in TypeScript is as follows:
If you want an interface/type that you can simply use without having to import it in the consuming module said interface must reside in a .ts
or ideally a .d.ts
file without any imports or exports being present in the same file. This is of utmost importance because as soon as you are exporting at least one thing from the same file it becomes a module and everything that is in that module must be subsequently imported by consumers.
To give you an example let's assume you want to have a type called Dictionary
that you want to be able to use without importing. The way to declare it would be as follows:
// types.d.ts
interface Dictionary {}
interface Foo {}
interface Bar {}
To use it you simply do:
// consumer.ts
const dict: Dictionary = {};
However, it will no longer work if for some reason any of the interfaces/types in that file are exported, e.g.:
// types.d.ts
interface Dictionary {}
interface Foo {}
export interface Bar {}
It will also not work if there are imports in that file:
// types.d.ts
import { OtherType } from 'other-library';
interface Dictionary {}
interface Foo extends OtherType {}
interface Bar {}
If that is the case the only way to be able to use the Dictionary
type would be to also export it and then import it in the consumer:
// types.d.ts
export interface Dictionary {}
interface Foo {}
export interface Bar {}
// consumer.ts
import { Dictionary } from './types';
const dict: Dictionary = {};
--isolatedModules
There is an additional quirk to keep in mind when using the isolatedModules
modules flag in TypeScript, which, importantly, is enabled by default (and cannot be disabled) when using Create React App - .ts
files MUST export at least one thing as otherwise you will be getting the "All files must be modules when the '--isolatedModules' flag is provided." error. That means that putting the Dictionary
interface in a types.ts
files without the export keyword won't work. It must either be an export from a .ts
file of be a declaration without the export in a .d.ts
file:
// types.d.ts
interface Dictionary {} // works
export interface Dictionary {} // works
// types.ts
interface Dictionary {} // doesn't work with --isolatedModules enabled
export interface Dictionary {} // works
N.B.
As @dtabuenc mentions in his answer ambient modules (.d.ts
files) are discoraged and my correction shouldn't be taken as advice. It's just an attempt at explaining how normal modules and ambient modules work in TypeScript.