microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.89k stars 12.47k forks source link

Importing files other than TS modules #2709

Closed jamiewinder closed 8 years ago

jamiewinder commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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.

MicahZoltu commented 9 years ago

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 commented 9 years ago

@Zoltu please see my comment in https://github.com/Microsoft/TypeScript/issues/3019#issuecomment-98821868 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{ }
}
jonrimmer commented 9 years ago

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 commented 9 years ago

@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 commented 9 years ago

@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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

+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"

geirsagberg commented 9 years ago

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
  }
}
masonicboom commented 9 years ago

Just want to echo the sentiment that an error message is scary. If it's non-fatal, feels like it should be a warning instead. As a newcomer considering TypeScript, this sends a strong message that the road ahead will be rocky.

aluanhaddad commented 9 years ago

:+1:

sarod commented 9 years ago

:+1: for import myTemplate: string from "./my-template.html"

aluanhaddad commented 9 years ago

I've been using TypeScript since it was first announced, and I absolutely love it. I've been using TypeScript with systemjs for a few days now, and I find it an extremely fun and productive workflow. I get all of the intellisense and errors from my IDE, while completely bypassing need for a file system build step. It really makes TypeScript feel like a first class language in the browser. Unfortunately the inability to specify HTML imports for my angular templates without getting tooling errors is quite frustrating. Everything works but I dislike tolerating any errors as they drown out other errors. I understand there is a workaround that involves declaring modules with dummy exports and using absolute paths but this feels very clumsy. I would love a way to inform the compiler that certain files should be treated as resources, perhaps via a type annotation, without losing the current advantages of strong type checking for modules.

serkanyersen commented 9 years ago

:+1: import template: string from "./templates/sidebar" :pray:

eesdil commented 9 years ago

+1 also would like to see this. For now I have solved it with generating ts/js files from the templates. I am referencing: "./my-template.html" and I am creating files like: my-template.html.ts and my-template.html.js (with content exported) With this ts compiler is happy and jspm is happy also.

asukaleido commented 9 years ago

:+1: for import myTemplate: string from "./my-template.html"

cschroeter commented 9 years ago

:+1: for import myTemplate: string from "./my-template.html"

slayerfat commented 9 years ago

:pray: import myTemplate: string from "./my-template.html"

MiguelAraCo commented 9 years ago

:+1: for import myTemplate: string from "./my-template.html"

elmariofredo commented 8 years ago

:heavy_plus_sign: :dolphin:

hourliert commented 8 years ago

:+1: !

Hotell commented 8 years ago

:+1:

shprink commented 8 years ago

https://medium.com/@bestander_nz/fighting-typescript-for-webpack-c5127b55ec86

lukasgeiter commented 8 years ago

:+1: Would love to see something along the lines of import myTemplate: string from "./my-template.html" being implemented.

stefan-leye commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

jack4it commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

antoineol commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

iterpugov commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

codebitmx commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

v7r7 commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

radotzki commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

davidreher commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

Mischi commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

aluanhaddad commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

tobich commented 8 years ago

:+1:

nschipperbrainsmith commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

Seems like a clean syntax and would help me immensely right now.

andreyctkn commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

arodik commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

toddwong commented 8 years ago

:+1:

mattdistefano commented 8 years ago

:+1: for import myTemplate: string from "./my-template.html"

amcdnl commented 8 years ago

My 2 cents: This seems rather ridiculous that I can't use external modules that don't having typings. I understand the value of typings but i'm not going to go and write typings for every module I use. It will end up users not using modules they want to use because there aren't TSDs for them. Also, if I were converting a project this would be a NIGHTMARE!

shairez commented 8 years ago

Warnings instead of Errors make sense, because although errors makes you take action, they can also scare people away or waste precious time like @jonrimmer did, which I think did a very good job summarizing how Angular 2 developers approach it.

Anyone from the typescript team can let us know if there are any plans for one of the following:

?

Thanks!