6

I have a js file in my project which I need to consume from a ts file.

The js file path is "javascript/jsToConsume.js".

The ts file path is "typescript/client.ts"

I've added a declarations file in the path "typings/internal/jsToConsume.d.ts", which its content is as follows:

declare namespace jsToConsume{
    export function func1(): void;
}

In my client.ts I try to consume it:

///<reference path="../typings/internal/jsToConsume.d.ts" />

import * as jsToConsume from '../javascript/jsToConsume'

But '../javascript/jsToConsume' is marked in a red line and I get the following error:

TS2307: Cannot find module '../javascript/jsToConsume'

BTW the code runs flawlessly, it's just a TSC error.

javascript/jsToConsume.js:

function func1(){
    return "Hello World";
}
exports.func1 = func1;

Any help will be profoundly appreciated!

10
  • are you using SystemJS to load the files? If you do you should configure it to correctly map the import to the actual js file. :) Commented Oct 17, 2016 at 22:19
  • @toskv I don't. it's a server side project in Node.js. Commented Oct 17, 2016 at 22:23
  • you'd still need to get the correct file loaded via require. :D maybe look into that? Commented Oct 17, 2016 at 22:25
  • @toskv my JavaScript version is ES6 and I transpile my TypeScript to ES6 so I don't use 'require'. This syntax works fine for me when I consume third party libraries that are installed in node_modules. Commented Oct 17, 2016 at 22:33
  • @Alon, node_modules is in your NODE_PATH (or some other equivalent variable) variable while the file you are trying to import is not. In this case you'll either need to modify NODE_PATH or provide a path to where your js file is Commented Oct 17, 2016 at 22:36

2 Answers 2

6
+100

If you pass the --allowJs flag as true in your tsconfig.json, it tells the TypeScript compiler to compile JavaScript files as well. Therefore, with this flag set to true, TypeScript will know about the modules you've defined in your JavaScript files, and you will not need to do any extra trickery with declaration files.

Therefore an example tsconfig.json file could look like this:

{
  "compilerOptions": {
    "allowJs": true
    "module": "commonjs",
    "noImplicitAny": true,
    "target": "es6"
  },
  "exclude": [
    "node_modules"
  ]
}

(https://www.typescriptlang.org/docs/handbook/compiler-options.html)

Of course, the config file would entirely depend on your project, but you would just add "allowJS": true as one of your "compilerOptions".

Note: This is available as of TypeScript 1.8

The relevant release notes are here:

https://www.typescriptlang.org/docs/release-notes/typescript-1.8.html#including-js-files-with---allowjs

-- EDIT --

In response to comments about requiring types along with your internal JS imports, I have come up with the following. However, if going to this much trouble to add types to your JavaScript modules, I would just suggest converting the file to TypeScript and typing all of your exports at the bare minimum (in fact, looking back at this edit, this seems really unnecessary unless it is absolutely impossible for whatever reason to convert your JS to TS). But anyway...

You would still pass "allowJs": true in your tsconfig.json, but you can create interfaces for the JavaScript modules you want, then type the imports in your TS file. The following provides an example, with the JS file and the TS file a little more fleshed out to show the possibilities:

Folder Structure

src
| - javascript
| | - jsToConsume.js
| - typescript
| | - client.ts
typings
| - typings.d.ts
tsconfig.json

jsToConsume.js

export const yourHair = (adjective) => {
    return `Your hair is ${adjective}`;
}

export let jam = 'sweet';

export class AnotherClass {
    constructor() {
        this.foo = 'bar';
    }
}

export default class Hungry {
    constructor() {
        this.hungry = true;
    }

    speak() {
        return 'More cake please';
    }
}

typings.d.ts

declare interface jsToConsumeModule {
    yourHair: (adjective: string) => string;
    jam: string;
    AnotherClass: AnotherClassConstructor;
}
declare interface Hungry {
    hungry: boolean;
    speak: () => string;
}
declare interface HungryConstructor {
    new (): Hungry;
}
declare interface AnotherClass {
    foo: string;
}
declare interface AnotherClassConstructor {
    new (): AnotherClass;
}

client.ts

import { yourHair as _yourHair_ } from './../javascript/jsToConsume';
const yourHair: (adjective: string) => string = _yourHair_;

import * as _jsToConsume_ from './../javascript/jsToConsume';
const jsToConsume: jsToConsumeModule = _jsToConsume_;

import _Hungry_ from './../javascript/jsToConsume';
const Hungry: HungryConstructor = _Hungry_;

So, when importing individual members and defaults from a module, just give each the desired type. Then you can give an interface for the public exports of the module when using import * as ....

NOTE But you must have a really good reason why you don't want to just change your JS files into TS. Think for a moment that you want types for your files, and you have control of them as they are internal to your project, so that sounds like the exact use case for why TS exists. You can't control external modules, so that's why you build declaration files to create an interface for interacting with the library. If you are determined to add types to your JavaScript, then you can do that by making it TypeScript.

Sign up to request clarification or add additional context in comments.

8 Comments

This is actually the only way of doing it properly. This is exactly how you make JS and TS work together with modules. Anything else is a hack
I don't want to compile js. I only want to be able to use an internal js file the same way I do with an external third party libraries. I can locate it in node_modules and imitate what other libraries do, but I want it located it my project, not in node_modules.
Sorry, maybe my wording was misleading, but this flag does exactly that. Leave your file where it is and the compiler will read the file and know the exports of it... You won't need to move the file to node_modules and the only reason you'd add a declaration file would be to give types to the properties and methods that are exported.
Just make sure you don't exclude your js files (apart from node_modules) in your tsconfig
To summarise, the allowjs flag is designed for exactly the situation where your project includes both TypeScript and JavaScript files
|
3

For external modules the problem is in line:

import * as jsToConsume from '../javascript/jsToConsume'

The code will even without it, because you have reference:

///<reference path="../typings/internal/jsToConsume.d.ts" />

Normal way to use external module is only have one line (https://www.typescriptlang.org/docs/handbook/modules.html):

import * as jsToConsume from 'jsToConsume';

And even better is to rename namespace to module:

declare module jsToConsume{...}

This was for external modules


But if you have only internal modules, it is better to use modules without namespace, just:

export function func1(): void;

Then you can use it as:

import {func1} from '../javascript/jsToConsume';

or

 import * as someName from '../javascript/jsToConsume';
someName.func1();

6 Comments

Thank you, I've removed the "import" statement and have only kept the script reference, and it did eliminate the error. However I disagree that the "module" keyword is better than "namespace". Afaik There are no more external and internal modules. External modules are just "modules" now and internal modules are "namespaces". The "module" keyword is for back support. See: typescriptlang.org/docs/handbook/namespaces.html
Sorry I've just seen that it actually didn't help me. It did eliminate the TSC error, but when I tried to run my project I got an error that "jsToConsume is not defined".
Can you please tell me how should I change my code exactly in my case? The old js file I try to consume uses external module.
It is enough to rewrite: declare module jsToConsume, then usage: import * as jsToConsume from 'jsToConsume'; or another old references way: ///<reference path="../typings/internal/jsToConsume.d.ts" /> on top of your ts file, usage is: jsToConsume.func1(). This should be enough
declare module 'jsToConsume' . Should be in quotes
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.