Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Importing files other than TS modules #2709

Closed
jamiewinder opened this issue Apr 10, 2015 · 92 comments
Closed

Importing files other than TS modules #2709

jamiewinder opened this issue Apr 10, 2015 · 92 comments
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript

Comments

@jamiewinder
Copy link

I've successfully been using SystemJS and TypeScript for a while now. I've been about to use the CommonJS syntax to easily import other TS modules, as well as other resources:

var OtherModule = require("./othermodule");
var Html = <string>require("./template.html!text");
require("./template.css!");

This is made possible by simply declaring the following function:

declare function require(name: string): any;

I'm now trying to do the same with the new ES6 syntax:

import { OtherModule } from "./othermodule";   // Works
import "./template.css!"; // Works
import Html from require("./template.html!text"); // Nope - 'Cannot find external module'

Of course I can do the same as before and declare and use 'require', but it'd be nice to keep it consistent. Perhaps assume that if an exclamation mark is in the path, then don't assume it's a TypeScript import?

@mhegazy
Copy link
Contributor

mhegazy commented Apr 10, 2015

if you can use absolute path you can do this today by doing something like:

// Define a module

declare module "template.html!text" {
    var html: number;
    export default x;
}

then your code would look like

import Html from "template.html!text";  // Html is a number

@dfaivre
Copy link

dfaivre commented Apr 11, 2015

It would be nice to not have to create manual declarations for every javascript module I want to import. I would think this keeps to the spirit of "Typescript is a superset of Javascript".

For example, I have an ES6 foo.js file sitting next to my bar.ts file. I'd love to just be able to:

import * as foo from 'foo.js'

and then have foo be an implicit any.

@mhegazy
Copy link
Contributor

mhegazy commented Apr 12, 2015

The problem is not the import, the problem is the type of your import (the type of foo); with no information about the shape of the imported module the compiler has no way of helping you. we have talked about something like:

import * as foo: IFoo from "foo";

Which would tell the compiler that you know shape of the module, and optionally you can pass any to just load the module with no checking.

@dfaivre
Copy link

dfaivre commented Apr 12, 2015

I actually tried something like that just as guess when I was playing around with it :)

The issue I think I'd still have is that I couldn't just take a bunch of es6 javascript files and one by one convert them to typescript without breaking everything. I love the fact that I used to be able to take a .js file, change the suffix to .ts, run it through the compiler and it would work.

I wonder if there could be a compiler flag like the impilictyAny option, something like allowImplicitAnyImports that would tell the compiler to only pull type information from the import if it is available. If you want to be strict about imports and/or use the explicit typing syntax you described, you could turn it to false.

@danquirk danquirk added the Suggestion An idea for TypeScript label Apr 13, 2015
@MicahZoltu
Copy link
Contributor

Using TypeScript 1.5-beta, transpiling to ES6.

import { Foo } from 'foo';
export class Bar {
    constructor() {
        new Foo();
    }
}

The above code complains:

Cannot find external module 'foo'.

However, the following JavaScript is generated:

import { Foo } from 'foo';
export class Bar {
    constructor() {
        new Foo();
    }
}

As far as static type checking goes, Foo is considered to be "any" so you can also call it as a function, use it in arithmatic, etc.

Interestingly, when building with gulp-typescript I don't get the error but atom-typescript does give me the error. I'm guessing there is a compiler option that controls this, but I am not sure what it is (which is why I am here).

@mhegazy
Copy link
Contributor

mhegazy commented May 4, 2015

@Zoltu please see my comment in #3019 (comment) to answer your question..

However, the following JavaScript is generated:

TypeScript errors do not block emit. it tells you the compiler did not understand some aspects of your code, but syntactic transformations still take place.

As far as static type checking goes, Foo is considered to be "any" so you can also call it as a function, use it in arithmatic, etc.

You need to tell the compiler the shape of the thing you are importing. currently the only way to do that is to write a .d.ts file e.g.:

declare module "foo" {
    export class Foo{ }
}

@mhegazy mhegazy added the In Discussion Not yet reached consensus label May 4, 2015
@jonrimmer
Copy link

I'm coming to this as a newcomer, having only recently started playing with TypeScript as part of the Angular 2 developer preview. I presume there's going to be plenty more people who follow a similar path, so let me capture my impressions:

I appreciate the value of typing, but it's not my primary motivation for using TypeScript. I'm more interested in decorators and the fact that its the recommended language for Angular 2. My expectation was that typing would be an optional addition that I could gradually apply, but not that I'd have to immediately find or create type definitions for all my code and its dependencies.

Whatever the reason, throwing errors on valid JavaScript code is confusing when TypeScript is explicitly described as a superset of JavaScript. The first thing many people will do is load up an existing app and try to compile it. Getting a bunch of errors in response doesn't give a great first impression.

That "Cannot find external module" error is really bad. At first I thought it meant the compiler couldn't find the file on disk, so I spent quite a while moving it around and trying to figure out what was wrong. I also thought that it meant the compiler hadn't output anything. Compiler errors that don't prevent compilation are rather odd. Shouldn't these be warnings, or at least the compiler should make it clear that they haven't prevented code emission?

I have a large, existing Angular 1 application, organised into a few hundred ES6 modules. Eventually I want to migrate it Angular 2, and thus probably TypeScript, but this is going to have to be a gradual process. Having the compiler throw hundreds of errors because my code lacks types isn't going to make this much fun.

@danquirk
Copy link
Member

danquirk commented May 5, 2015

@jonrimmer This is good feedback. Would be curious to here what your preferred workflow would've been.

As far as the module errors go, I totally agree they're pretty bad and very painful to debug.

As for the superset/errors part, what behavior would you prefer? Keep in mind the errors you're seeing are more like warnings in most languages, they can be ignored and JS is still emitted (were you using VS or some other editor?). Then you can fix up those errors over time. Some of them are to satisfy the compiler, some might be actual bugs in your code. The hope is that you can grab an existing .d.ts from DefinitelyTyped for much of your library code and fix any errors related to common code. We also have some ideas for ways to ease this process further (ex #2916).

If we didn't give those errors (ex some compiler flag like --noTypeErrors) then what would really be the point in starting to write with TypeScript? It'd be identical to your JavaScript except with an additional compile step in your workflow. Eventually you'd have to turn off the --noTypeErrors flag and be back in this state with many errors due to lack of type information. If you only got errors on the code you explicitly annotated then you'd actually have to write way more type annotations than necessary to get the type checking we advertise as powerful and useful.

@jonrimmer
Copy link

@danquirk I am using Visual Studio Code. I can live with the current behaviour, but I wish the compiler's messaging was clearer. Something like "Warning: No registered type information for external module foo — using implicit 'any' type." If you have to keep calling them errors, some message making it clear that code is still being emitted would be helpful. @mhegazy's suggestion of being able to add an explicit type, even if it's any, to the import to shut up the compiler would be nice as well.

As for using TypeScript with untyped code, I actually get a lot. First, I get all the other ES6 and ES7 features you guys have implemented. In particular, I get to use Angular 2's annotations, which require decorator support. If you compare the TS vs ES5 examples in the Angular 2 documentation you can see that the ES5 equivalent code isn't nice, whereas the TS version is pretty awesome. That alone would be enough to make me use TS for my Angular 2 code, regardless of typing.

Also, even if my existing codebase and some of its dependencies aren't typed, by using TS for compilation, I can write all my new code with types, and gradually add them to the old code. I can also consume the core Angular 2 libraries in their original TS form, and get intellisense and checking on my use of them, at least.

Finally, I think maybe there's a question of project philosophy to consider here. It seems like until recently TypeScript existed in a niche where it existed to provide JavaScript + Types to people who wanted them, and could afford to be somewhat uncompromising in how it applied that vision. But its recent enhancements have turned it into more of a general ES.next compiler along the lines of Babel, and maybe that requires a reconsideration of how hard it pushes typing, vs. making interop with untyped code as painless as possible. I'm optimistic that types will make it into JS proper eventually. But with the best will in the world, there is going to be more untyped JS code around than not for a long time to come.

@danquirk
Copy link
Member

danquirk commented May 5, 2015

But what do you expect to happen with your untyped code? Do you want some way to turn off all type checking for a file? I get that the migration initially involves a lot of type errors but what do you envision instead? Even with file level settings as soon as you want to add type annotations to one function in that file you need to deal with the many errors from that file.

The entire purpose of emitting in the face of these type errors is to acknowledge how much untyped code is out there. Then the migration process to TypeScript can happen piecemeal without having broken your entire application until you've dealt with every single type error.

TypeScript is definitely aiming to add value in the form of ES6/7/etc features before they're in every browser/engine. But the fundamental point of the project is types (hence the name :)). There have always been type-less transpilers and will continue to be. What parts of the migration process are particularly painful to you with the .d.ts on DefinitelyTyped and if #2916 existed to easily get .d.ts for your JSDoc'd code? (I assume a bunch of code without JSDocs would be one).

@dfaivre
Copy link

dfaivre commented May 5, 2015

I seem to recall at one of Anders talks at Build 2015, he again referenced how any valid javascript file was a valid typescript file. I'd vote for, again, having a compiler switch similar to allowing implicit anys. If typescript can find a type for a module import, use it, else use "any". If people want to be strict, they can set it appropriately. For cases where you are starting new with Typescript, you can set the flag to strict, and then for external, untyped modules, use some sort of language construct (see @mhegazy idea above) to explicitly set the import type to "any". For existing code bases, you can set the compiler to allow implicit any imports.

I really love Typescript, it's a wonderful, powerful tool. I think it's adoption has been so high in part because it's so flexible (both in the optional typing and it's cross platform behavior). It plays nice with the existing Javascript ecosystem. The current module implementation unfortunately does not.

@RyanCavanaugh RyanCavanaugh added Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. and removed In Discussion Not yet reached consensus labels May 19, 2015
@RyanCavanaugh
Copy link
Member

Is Mohamed's suggestion of

import * as foo: IFoo from "foo";

enough to satisfy all the use cases here? Basically the logic would be that if we see a type annotation (: IFoo in this example) on the target of an import, we would "skip" resolution of the "foo" module and trust that you knew what you were doing.

@dfaivre
Copy link

dfaivre commented May 19, 2015

It would work for my current cases, where I'm starting with a Typescript code base and want to import a js library without any type defs. Partial conversion of an es6 .js project to .ts still seems more painful than it needs to be (going through and adding : any annotations to all your imports), but that may just be a very slim use case.

@lowkay
Copy link

lowkay commented May 26, 2015

Howdy,

Dealing with a large existing untyped codebase and porting it to TypeScript is something that does and will happen frequently, I agree with the sentiments @dfaivre in this respect - there should be an 'implicitly any' flag for the compiler.

The idea of typing import * as foo: any from "foo" may bring issues when you properly convert "foo" into typescript - now you "want" to make sure everything adheres to the module type definition, but the compiler will ignore it unless you go back through your code again(!) and remove the typing from the import.

I think typescript should make the transition easy and make saying "now I care about the types of this module" simple - so I don't have to edit existing typescript code to make it all care - I can do it through one (or two) actions:

  1. Provide a typing file / port a module to typescript (partial modules should throw errors if there's missing members/methods in use) - i.e. now I care about the types in this module,
  2. Tell the compiler that I now want to treat all missing module typings as errors - i.e. now I care about the types in every module.

To achieve this I think these things need to happen:

  1. reduce the ERROR to a WARN, errors are scary, maybe it should be scary if a project is typescript and has been for a while, otherwise it's just confusing and off-putting,
  2. to prevent cascading warnings and to allow the default 'I don't care about types here', type any unresolved module type to 'any',
  3. Add a compiler flag to treat module type resolution warnings as errors.

Two sides to the same story of course - but I think you can cater for both camps:

  1. New developers - to increase adoption and make the transition simpler, the defaults should be easy to understand and not scare developers away but guide them to leverage typing,
  2. Existing developers - want all the potential warnings to flag up, but they are existing developers and know the TypeScript compiler more intimately so will have no trouble activating a flag to treat the warnings as errors.

In terms of what to do when partially migrating source to TypeScript, those partially typed modules may now throw errors - but that's a good thing - by partially typing you're indicating that you do now care about the type of the module.

@danquirk
Copy link
Member

This is good feedback on migration pain. I would just note that of your 3 suggestions there 2 are actually in place, just somewhat poorly communicated. Our errors are actually more like warnings in the sense that they will not block a build/emit phase. So while your migrated JavaScript may have errors reported by the TypeScript compiler (including module type resolution ones) you should still be getting emit that makes sense for the most part (syntax errors are another story).

@frankwallis
Copy link
Contributor

+1 for import * as foo: IFoo from "foo"; and variations

That would support importing html strings and json objects,

e.g.
import myTemplate: string from "./my-template.html"
import myConfig: IConfig from "my-config.json"

@mhegazy mhegazy added In Discussion Not yet reached consensus and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Sep 3, 2015
@geirsagberg
Copy link

One serious issue with treating untyped modules as errors is that it can fail the build process in Visual Studio and MSBuild in general. I have had to explicitly disable the "No emit on error" checkbox for a lot of projects; I feel it would be much better if untyped modules were raher shown as warnings.

It also fails browserify, e.g. this code in index.ts:

import * as $ from 'jquery';

$(() => {
    alert('hello world');
});

when compiled with this command:

browserify index.ts -p tsify --outfile bundle.js

will fail with the error:

TypeScript error: index.ts(1,20): Error TS2307: Cannot find module 'jquery'.

On another note, if it was possible to explicitly ignore specific TS**** errors, that would be an acceptable workaround, e.g. something like this in tsconfig.json:

{
  ignoreRules: {
    "TS2307": true
  }
}

@mhegazy
Copy link
Contributor

mhegazy commented Jul 3, 2016

if you are using typescript@next you can use short-hand module declarations combined with wildcard module names to switch off module verification for certain groups of modules e.g.

declare module "npm-package\*";  // any module import with this prefix is considered `any`.

see https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#shorthand-ambient-module-declarations

@empz
Copy link

empz commented Jul 4, 2016

@mhegazy That's better but you still have to declare module for every npm package you want to play with.

Why can't just TypeScript do import * as Package from "npm-package" and set Package type to any if it can't find a type declaration for it?

@mhegazy
Copy link
Contributor

mhegazy commented Jul 4, 2016

if you want to disable all module checking, use "declare module "*";. I should warn here that a lot of the value that the TS tooling provide relies on getting type information.

@empz
Copy link

empz commented Jul 4, 2016

@mhegazy I don't want to disable all module type checking. I have npm packages that include their own d.ts file, I also use typings for other d.ts files and then, there are some packages for which d.ts files do not exist anywhere.
By the time I want to do some quick playing with a new library (that I might not end using after all), I'd just rather import it as any without declaring anything. Later, if I decide I'll be using the library, I might create a d.ts file if it doesn't exist yet.

But according to what I've been reading, this is not possible and it won't be possible either in 2.0. A declare module... line has to be there for a module without declarations.

@mhegazy
Copy link
Contributor

mhegazy commented Jul 4, 2016

if a declaration file is found the compiler uses it. if not, it issues an error for unknown module. if i understand correctly you do not want to see this error. declare module "*"; effectively shuts off this error. if the declaration file is there, it will always be loaded. not sure i see what else can be done here.

@geirsagberg
Copy link

geirsagberg commented Jul 4, 2016

declare module "*"; should match only the modules that don't have explicit declarations, correct? So given the following code:

# declarations.d.ts
declare module '*';

# index.ts
import * as $ from 'jquery';
import * as _ from 'lodash';

If I have only installed typings for jquery, I would expect intellisense for $, while _ would be treated as any.

EDIT: Actually I can confirm that this is working just as intended, after setting typescript.tsdk in VSCode and installing typescript@next. I am getting full intellisense for jQuery, while lodash is treated as any. Awesome 👍

@daslicht
Copy link

daslicht commented Sep 17, 2016

@geirsagberg:
I created a declaration.d.ts in the typings folder, added declare module '*';
and added the following the index.d.ts:
/// <reference path="declarations.d.ts" />
But I still get a error when I try the following:

import template from './app.component';

What else is needed ? please

@born2net
Copy link

isn't

# declarations.d.ts
declare module '*';

dangerous as you won't get any real errors either on missing modules?

I am still having an issue with this (Sep 2016)

I am using systemjs with the text plugin and angular2, so I am importing templates as:
import RatesTableTemp from './RatesTable.html!text';

@Component({
    selector: 'RatesTable',
    moduleId: __moduleName,
    changeDetection: ChangeDetectionStrategy.OnPush,
    styles: [`
        .rateInput {
            width: 40px; 
            color: #0f0f0f;
        }
        .btn span.fa {                
            opacity: 0;                
        }
        .btn.active span.fa {                
            opacity: 1;                
        }
    `],
    template: RatesTableTemp
})

everything works great
but TS gives an annoying error
of

 Error:(10, 28) TS2307:Cannot find module './RatesTable.html!text'.

any way to quiet the error down?

@mhegazy
Copy link
Contributor

mhegazy commented Sep 19, 2016

any way to quiet the error down?

declare module '*!text' {
    var _: string;
    export default  _;
}

@born2net
Copy link

thanks sooooo much!
Sean.

@pselden
Copy link

pselden commented Feb 3, 2017

Is there anything special I have to do to reference the .d.ts file so that my imports work? I can't seem to get the solutions listed here to work.

// webpack.d.ts
import {IUseableStyle} from './common/IUseableStyle';

declare module '*.html' {
    var _: string;
    export default  _;
}

declare module '*.scss' {
    var _:  IUseableStyle;
    export default  _;
}
// component.ts

import template from './component.html'; // [ts] cannot find module './component.html';

tsconfig.json looks like:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "noImplicitAny": true,
    "outDir": ".tmp/src/",
    "allowJs": true,
    "target": "es6",
    "allowUnreachableCode": true
  },
  "include": [
    "/src/**/*"
  ]
}

@mhegazy
Copy link
Contributor

mhegazy commented Feb 3, 2017

Your .d.ts is a module (i.e. it has a top level import or export), modules have their own scope. this makes all declaration in this file not visible outside it. your file should look like:

declare module '*.html' {
    var _: string;
    export default  _;
}

declare module '*.scss' {
    import {IUseableStyle} from './common/IUseableStyle';
    var _:  IUseableStyle;
    export default  _;
}

@bomzj
Copy link

bomzj commented Mar 20, 2017

Wildcard is not working for me , maybe it's due to I use typescript 1.8.
This doesn't work:

declare module '*.html' {
    var _: string;
    export default  _;
}

The following works fine:

import "text!Components/Templates/MyTemplate.html";
...
var template = require('text!Components/Templates/MyTemplate.html');

@empz
Copy link

empz commented Mar 20, 2017

@bomzj Wildcard only works in TS 2.0 or higher.

@fiznool
Copy link

fiznool commented Mar 29, 2017

EDIT: never mind. It seems that the README for the svg-inline-loader plugin is wrong. You need to use 'svg-inline-loader' as the loader name.

// webpack.config.js
config.module.loaders.push({
  test: /\.svg$/,
  loader: 'svg-inline-loader'
});

I've tried to follow through this issue but I'm a bit lost and would appreciate some help.

I'm trying to load a SVG file into my .ts file, so I can use it in my HTML template (Angular 2) as an inline SVG. This allows me to change the colour of the SVG with CSS. I'm using web pack 1.x.

This is what I've tried:

// webpack.config.js
config.module.loaders.push({
  test: /\.svg$/,
  loader: 'svg-inline'. // Installed locally with `npm install svg-inline-loader`
});
// declarations.d.ts
declare function require(string): any;

declare module "*.svg" {
  const content: any;
  export default content;
}
// Tried both this:
import damSvg from '../../../assets/icon/barrier-types/dam.svg';

// And this:
const damSvg = require('../../../assets/icon/barrier-types/dam.svg');

In both cases, I get a webpack 'Cannot find module' error.

Can anybody shed any light on what I'm doing wrong?

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Mar 29, 2017

You either have the wrong path or you haven't listed .svg as a resolvable extension.

resolve: ['.svg', '.ts']

Your question would probably be better asked on stack overflow. it also has nothing to do with TypeScript , being about Webpack loading neither TypeScript nor JavaScript files

@fiznool
Copy link

fiznool commented Mar 29, 2017

@aluanhaddad thank you for taking the time to respond. It ended up being an error with the loader I was using. I've updated my post above.

@aluanhaddad
Copy link
Contributor

I didn't realize your issue was resolved (but I'm glad to hear it). I guess I'm used to seeing edits in bold.

@avindra
Copy link

avindra commented Apr 16, 2017

Is there a way to import a file, raw as is, as a string, using Typescript purely?

I dont want to involve SystemJS, webpack or rollup to solve this problem.

@kitsonk
Copy link
Contributor

kitsonk commented Apr 17, 2017

TypeScript does not provide run-time functionality. If emitted TypeScript runs under NodeJS this functionality is already there. There are other libraries that provide similar functionality in the browser.

@lokeshdaiya
Copy link

@avindra
Did you get any solution for your problem. I am also trying to import markdown(.md) files in typescript without using webpack and other module.

I have written in typing.d.ts

declare module *!txt* { const value: any; export default value; }

in TS file

import * as readme from "./README.md!txt"; // I am getting error for this.

@kitsonk could you suggest library which can be used to import non-code module in typescript.

@Izhaki
Copy link

Izhaki commented Jul 11, 2017

Nothing worked for me, until I've changed my import to the * as <x> format.

This works with TS 2.4.1:

declare module '*.pegjs' {
    var _: string;
    export default _;
}

Then:

import * as cypher from './grammar/cypher.pegjs'

And the relevant parts of webpack config:

var config = {

    resolve: {
        extensions: [ '.ts', '.js', '.pegjs' ],
    },

    module: {
        rules: [{
            test: /\.pegjs$/,
            use: 'raw-loader',
        }]
    },
}

module.exports = config;

@quantuminformation
Copy link

I tried this:

declare module "mirage-server"

import MirageServer, { Factory } from 'mirage-server';

but get:

error TS2665: Invalid module name in augmentation. Module 'mirage-server' resolves to an untyped module at '/Users/nikos/WebstormProjects/client/node_modules/mirage-server/lib/index.js', which cannot be augmented.

@Retsam
Copy link

Retsam commented Mar 8, 2018

@quantuminformation You can't put the declare module line in the same file as the imports, it's got to go into a .d.ts file.

@microsoft microsoft locked and limited conversation to collaborators Jul 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests