Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consume internal javascript file from typescript

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!

like image 585
Alon Avatar asked Oct 17 '16 22:10

Alon


2 Answers

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.

like image 86
theRealRobG Avatar answered Nov 16 '22 12:11

theRealRobG


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();
like image 24
Artem Avatar answered Nov 16 '22 12:11

Artem