How to import JS file wrapped in IIFE into TS - javascript

I have a TS file, and I want to import the functionality of a JS library that is wrapped in an IIFE. The library currently exposes its functionality by setting properties on the window object. How can I import this library into TS? I've tried adding exports before each function, but I get the error file is not a module. Should I just bite the bullet and copy the code over manually?
To avoid the XY problem, I'm making a website, and both the front and back end have some common functionality that they need to perform. I'm looking into a serverless back end using TS. If that's an anti-pattern and I should do something else entirely, I'll accept that as an answer too.

I didn't find a particularly elegant way to do this, but I got the functionality I wanted by returning the specific functions I need from the anonymous function and exporting the results of the IIFE.
Like so:
JS Library (I added the export default and the return { ... })
export default (function () {
function foo() { ... }
function bar() { ... }
function baz() { ... }
...
// the library makes some functions available to the window
window['baz'] = baz
// I manually export the functions I need
return {
'foo': foo
'bar': bar
}
})()
TS file:
import lib from './library.js'
lib.bar()
I also had "esModuleInterop": true in my tsconfig.json file

I accomplished this by publishing and installing an NPM package that contains a custom definition for a library and then tweaking the tsconfig.json to allow it.
Create an index.d.ts file in the same directory as your IIFE library .js file
In index.d.ts, add the single line: declare var libname = require('libname');
For example:
declare var identikon_cljs = require('identikon_cljs');
Publish it as an npm organization or user scoped package: https://docs.npmjs.com/creating-and-publishing-scoped-public-packages
Install the package in your project (e.g. npm i #mpm/identikon --save)
Add or set the following compilerOptions as true to prevent a TypeScript complilation error stating "Initializers are not allowed in ambient contexts":
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
}
}
Import the library in your component like so:
import '#mpm/identikon';
You may then access properties and methods of the library as though it was exported as a module. Here is an example of a directive I made:
import { Directive, ElementRef, Input, } from '#angular/core';
import '#mpm/identikon';
#Directive({
selector: '[identikon]'
})
export class Identikon {
#Input() width: number = 64;
#Input() height: number = 64;
#Input() set identikon(value: string) {
if (value) {
let id = 'identikon-' + value + '-' + new Date().getTime().toString();
this.el.nativeElement.setAttribute('id', id);
identikon_cljs.core.make_identikon('#' + id, this.width, this.height, value);
}
};
constructor(private el: ElementRef) { }
}
Then I could bind it with ease in an Ionic 2/3 project like so:
<ion-avatar [identikon]="user.avatar" [width]="32" [height]="32" item-end></ion-avatar>

Related

Pulling in types for use in a .d.ts file with declare module without making it a module file

I am attempting to write a type declaration for an NPM package (or more specifically an untyped directory within a package) my project depends on.
The package itself is react-big-calendar and it doesn't bundle its own types, however there is #types/react-big-calendar which provides types for the main package, but not for the react-big-calendar/lib/addons/dragAndDrop "sub-package" it has in itself.
The above gets me working import BigCalendar from 'react-big-calendar' which is great, and I want to also get working import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop' so I figured I'd just declare module my way there.
I cannot place the declare module statement in any TSX file, because it has to be in its own file which is not an ES module, but it also cannot be an import+export free TS file, because I am also using CRA which enforces isolatedModules and so disallows non-module TS/X files.
I can and should place it in a .d.ts file, like this:
declare module 'react-big-calendar/lib/addons/dragAndDrop' {
function withDragAndDrop(calendar: any): any;
export = withDragAndDrop;
}
This looks fine, but is not much of an improvement typing-wise. The function I am looking to type basically takes a React component and returns it with some extra props. But even to just type is as a function which takes the specific BigCalendar component and returns it is a problem, because I cannot use an import statement (to pull in the component type) in the d.ts file. If I do, it turns into a module file and that breaks the declare module statement.
I am looking for something like this:
declare module 'react-big-calendar/lib/addons/dragAndDrop' {
function withDragAndDrop(calendar: BigCalendar): typeof BigCalendar & {
props: {
extraProp1: string;
// …
extraPropN: string;
}
};
export = withDragAndDrop;
}
With that I should be able to use the HOC like this: const DragAndDropCalendar = withDragAndDrop(BigCalendar); followed by <DragAndDropCalendar originalProp={value} extraProp1={value} />.
The thing that is missing is pulling in the types to the .d.ts file in a way which doesn't turn it into a module breaking the declare module statement stripping me of types, bringing me to square one again.
What options do I have there? I tried to use require but that returns any and I couldn't figure out if <reference is the right tool here or not.
I figured out how to import the original component types (React Big Calendar in this case, but the solution is generic) in the typings (which in this case are for the RBC drag and drop addon).
withDragAndDrop.d.ts:
declare module 'react-big-calendar/lib/addons/dragAndDrop' {
import BigCalendar, { BigCalendarProps, Event } from 'react-big-calendar';
type withDragAndDropProps<TEvent> = {
onEventDrop: (args: { event: TEvent, start: stringOrDate, end: stringOrDate, allDay: boolean }) => void;
onEventResize: (args: { event: TEvent, start: stringOrDate, end: stringOrDate, allDay: boolean }) => void;
};
declare class DragAndDropCalendar<TEvent extends Event = Event, TResource extends object = object>
extends React.Component<BigCalendarProps<TEvent, TResource> & withDragAndDropProps<TEvent>>, {}
function withDragAndDrop(calendar: typeof BigCalendar): typeof DragAndDropCalendar;
export = withDragAndDrop;
};
Usage:
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop';
import "react-big-calendar/lib/addons/dragAndDrop/styles.css";
const DragAndDropCalendar = withDragAndDrop(BigCalendar);
// TSX:
<DragAndDropCalendar<MyEvent> … onEventDrop onEventResize />

typescript declare third party modules

How could I declare a third party module which looks like this:
in third party module:
module.exports = function foo(){
// do somthing
}
in my code:
import * as foo from 'foo-module'; // Can not find a declaration module for ...
foo();
Check out the documentation on working with 3rd party modules.
How to write the declaration depends a lot on how the module was written and what it exports.
The example you've given is a CommonJS module (module.exports = ...) which is not really a valid ES6 module, because ES6 cannot export a function as the module (it can only export function members or a default function).
Update for TypeScript 2.7+
With the added esModuleInterop compiler option you no longer need to use the "namespace hack" shown below for CommonJS modules that have a non-ES6 compatible export.
First, make sure you've enabled esModuleInterop in your tsconfig.json (which is now included by default with tsc --init):
{
"compilerOptions" {
...
"esModuleInterop": true,
...
}
}
Declare your foo-example in a .d.ts file like this:
declare module "foo-module" {
function foo(): void;
export = foo;
}
Now you can import it as a namespace like you wanted:
import * as foo from "foo-module";
foo();
Or as a default import:
import foo from "foo-module";
foo();
Older workaround
You can declare your foo-example in a .d.ts file like this:
declare module "foo-module" {
function foo(): void;
namespace foo { } // This is a hack to allow ES6 wildcard imports
export = foo;
}
And import like you wanted:
import * as foo from "foo-module";
foo();
Or like this:
import foo = require("foo-module");
foo();
The documentation has a good resource on declaration files and some templates for various kinds of declaration files.
I had a similar problem. And struggled to add a type definition to my project. Finally, I was able to achieve it using the following steps.
This is some module (just with constants), lets call it some-module - node_modules/some-module/index.js.
'use strict';
exports.__esModule = true;
var APPS = exports.APPS = {
ona: 'ona',
tacq: 'tacq',
inetAcq: 'inetAcq'
};
First I add to tsconfig.json baseUrl and typeRoots
{
...
"compilerOptions": {
...
"baseUrl": "types",
"typeRoots": ["types"]
}
...
}
Second in my project root I create folder types with same folders structure for the module types/some-module/index.js and place the code:
declare module 'some-module' {
type Apps = {
ona: string;
tacq: string;
inetAcq: string;
};
let APPS: Apps
}
Finally I can import it in my my-file.ts with typings!
import { APPS } from 'some-module';

Typescript and AngularJS 1.5: How to handle export class

I have this module.ts file:
import { IHttpService, IPromise } from 'angular';
export class ProductService {
static $inject = ["$http"];
constructor(private $http: IHttpService) { }
loaded: boolean = false;
promise: IPromise<{ data: any }>;
getProducts(): IPromise<{ data: any }> {
if (!this.loaded) {
this.loaded = true;
this.promise = this.$http.get('/products.json');
}
return this.promise;
}
}
var module = angular.module("psShopping", ["ngComponentRouter"]);
module.value("$routerRootComponent", "shoppingApp");
module.service('products', ProductService);
and this gets transpiled/compiled to module.js:
"use strict";
var ProductService = (function () {
function ProductService($http) {
this.$http = $http;
this.loaded = false;
}
ProductService.prototype.getProducts = function () {
if (!this.loaded) {
this.loaded = true;
this.promise = this.$http.get('/products.json');
}
return this.promise;
};
ProductService.$inject = ["$http"];
return ProductService;
}());
exports.ProductService = ProductService;
var module = angular.module("psShopping", ["ngComponentRouter"]);
module.value("$routerRootComponent", "shoppingApp");
module.service('products', ProductService);
The problematic line is: exports.ProductService = ProductService; If I remove this manually - the app works perfectly. But if I leave this like that, in the browsers console I get an error "exports is not defined".
What can be done about this? I can't just remove "export" from the .ts file as this class is used in other files (I import it there). I tried different compiler options, but it just gives me different errors (like "define is not defined" etc) and I'm not sure how to handle this. maybe theres a way that the tsc would just not produce this line?
Whenever you use the import and export keywords in typescript, you are converting the file to act as a module.
Modules are designed to work along with module systems like commonjs,amd, es6, systemjs etc...
If your are trying to load a raw script in a browser without using a module-loader or bundler such as systemjs, webpack, browserify, jspm, etc... then you cannot use external modules (so you can't use import/exports).
You can still use typings from definitely typed, but you have to load them through ///<reference /> tags.
If using typescript 2.0 typings like angular are also available and imported by default when you run
npm install #types/angular
Usually these typings will expose global namespace declaration that you will now be able to use throughout your application.
For example in your case angular exposes an ng namespace and you would use it like:
promise: ng.IPromise<{ data: any }>;
You can also easily alias it:
import IPromise = ng.IPromise
Note that this import behaves differently than module imports (it just aliases).
If you are going to do anything other than a trivial application, I would strongly recommend you use a module loader such as webpack to compile and bundle the application for the browser. There are many limitations to just using plain scripts.

How to create a .d.ts file that exports an existing module?

I am using both dust.js (specifically, dustjs-linkedin) and dustjs-helpers in my TypeScript project. I got the typings for dustjs-linkedin off of definitelyTyped, but I am having trouble with the dustjs-helpers. Pretty much, I just want to declare a module called dustjs-helpers and have it correctly export the dustjst-linkedin module. That means that any time you call import helpers = require('dustjs-helpers'); you should be able to access all of the functions that regular dust uses by default.
Dust's typings file declares its module as following: declare module "dustjs-linkedin" { ... }. I was hoping that I could do something like the following, but I get errors...
/// <reference path="../dustjs-linkedin/dustjs-linkedin.d.ts" />
declare module "dustjs-helpers" {
import dust = require("dustjs-linkedin")
export = dust;
}
Can anyone help me out?
A bit involved but I have verified that this works:
declare module "dustjs-helpers" {
import dust = require("dustjs-linkedin")
// Bring into a type
type Dust = typeof dust;
// Specify extensions
type Extensions = {
anotherFunc : Function;
}
// Combine types
type DustExtended = Dust & Extensions;
// Create var for export
var dustExtended: DustExtended;
// Export
export = dustExtended;
}

TypeScript ES6 import module "File is not a module error"

I am using TypeScript 1.6 with ES6 modules syntax.
My files are:
test.ts:
module App {
export class SomeClass {
getName(): string {
return 'name';
}
}
}
main.ts:
import App from './test';
var a = new App.SomeClass();
When I am trying to compile the main.ts file I get this error:
Error TS2306: File 'test.ts' is not a module.
How can I accomplish that?
Extended - to provide more details based on some comments
The error
Error TS2306: File 'test.ts' is not a module.
Comes from the fact described here http://exploringjs.com/es6/ch_modules.html
17. Modules
This chapter explains how the built-in modules work in ECMAScript 6.
17.1 Overview
In ECMAScript 6, modules are stored in files. There is exactly one
module per file and one file per module. You have two ways of
exporting things from a module. These two ways can be mixed, but it is
usually better to use them separately.
17.1.1 Multiple named exports
There can be multiple named exports:
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
...
17.1.2 Single default export
There can be a single default export. For example, a function:
//------ myFunc.js ------
export default function () { ··· } // no semicolon!
Based on the above we need the export, as a part of the test.js file. Let's adjust the content of it like this:
// test.js - exporting es6
export module App {
export class SomeClass {
getName(): string {
return 'name';
}
}
export class OtherClass {
getName(): string {
return 'name';
}
}
}
And now we can import it in these three ways:
import * as app1 from "./test";
import app2 = require("./test");
import {App} from "./test";
And we can consume imported stuff like this:
var a1: app1.App.SomeClass = new app1.App.SomeClass();
var a2: app1.App.OtherClass = new app1.App.OtherClass();
var b1: app2.App.SomeClass = new app2.App.SomeClass();
var b2: app2.App.OtherClass = new app2.App.OtherClass();
var c1: App.SomeClass = new App.SomeClass();
var c2: App.OtherClass = new App.OtherClass();
and call the method to see it in action:
console.log(a1.getName())
console.log(a2.getName())
console.log(b1.getName())
console.log(b2.getName())
console.log(c1.getName())
console.log(c2.getName())
Original part is trying to help to reduce the amount of complexity in usage of the namespace
Original part:
I would really strongly suggest to check this Q & A:
How do I use namespaces with TypeScript external modules?
Let me cite the first sentence:
Do not use "namespaces" in external modules.
Don't do this.
Seriously. Stop.
...
In this case, we just do not need module inside of test.ts. This could be the content of it adjusted test.ts:
export class SomeClass
{
getName(): string
{
return 'name';
}
}
Read more here
Export =
In the previous example, when we consumed each validator, each module only exported one value. In cases like this, it's cumbersome to work with these symbols through their qualified name when a single identifier would do just as well.
The export = syntax specifies a single object that is exported from the module. This can be a class, interface, module, function, or enum. When imported, the exported symbol is consumed directly and is not qualified by any name.
we can later consume it like this:
import App = require('./test');
var sc: App.SomeClass = new App.SomeClass();
sc.getName();
Read more here:
Optional Module Loading and Other Advanced Loading Scenarios
In some cases, you may want to only load a module under some conditions. In TypeScript, we can use the pattern shown below to implement this and other advanced loading scenarios to directly invoke the module loaders without losing type safety.
The compiler detects whether each module is used in the emitted JavaScript. For modules that are only used as part of the type system, no require calls are emitted. This culling of unused references is a good performance optimization, and also allows for optional loading of those modules.
The core idea of the pattern is that the import id = require('...') statement gives us access to the types exposed by the external module. The module loader is invoked (through require) dynamically, as shown in the if blocks below. This leverages the reference-culling optimization so that the module is only loaded when needed. For this pattern to work, it's important that the symbol defined via import is only used in type positions (i.e. never in a position that would be emitted into the JavaScript).
Above answers are correct. But just in case...
Got same error in VS Code. Had to re-save/recompile file that was throwing error.
How can I accomplish that?
Your example declares a TypeScript < 1.5 internal module, which is now called a namespace. The old module App {} syntax is now equivalent to namespace App {}. As a result, the following works:
// test.ts
export namespace App {
export class SomeClass {
getName(): string {
return 'name';
}
}
}
// main.ts
import { App } from './test';
var a = new App.SomeClass();
That being said...
Try to avoid exporting namespaces and instead export modules (which were previously called external modules). If needs be you can use a namespace on import with the namespace import pattern like this:
// test.ts
export class SomeClass {
getName(): string {
return 'name';
}
}
// main.ts
import * as App from './test'; // namespace import pattern
var a = new App.SomeClass();
In addition to A. Tim's answer there are times when even that doesn't work, so you need to:
Rewrite the import string, using the intellisense. Sometimes this fixes the issue
Restart VS Code
I had this issue and I had forgotten to export the Class.
In addition to Tim's answer, this issue occurred for me when I was splitting up a refactoring a file, splitting it up into their own files.
VSCode, for some reason, indented parts of my [class] code, which caused this issue. This was hard to notice at first, but after I realised the code was indented, I formatted the code and the issue disappeared.
for example, everything after the first line of the Class definition was auto-indented during the paste.
export class MyClass extends Something<string> {
public blah: string = null;
constructor() { ... }
}
Just in case this may works for you as it did form me, i had this files
//server.ts
class Server{
...
}
exports.Server = Server
//app.ts
import {Server} from './server.ts'
And this actually raised an error but i changed server.ts to
//server.ts
export class Server{
...
}
and it worked 😎👌
Note: i am using this config
"target": "esnext",
"module": "commonjs",
I faced the same issue in a module that has no exports. I used it for side-effects only. This is what the TypeScript docs say about importing side-effects modules:
Though not recommended practice, some modules set up some global state that can be used by other modules. These modules may not have any exports, or the consumer is not interested in any of their exports. To import these modules, use:
import "./my-module.js";
In that situation, you can fix the "File is not a module" error by simply exporting an empty object:
// side-effects stuff
export default {};
I faced the same issue ("File is not a module error") for import js in vue component
import handleClientLoad from "../../../public/js/calendar.js"
I do this and solve it
// #ts-ignore
import handleClientLoad from "../../../public/js/calendar.js"
The file needs to add Component from core hence add the following import to the top
import { Component } from '#angular/core';

Categories