Typescript: Support shorthand ambient module declarations and wildcard chars in module names

Created on 25 Jan 2016  ·  74Comments  ·  Source: microsoft/TypeScript

follow up on https://github.com/Microsoft/TypeScript/issues/6614

Related issues: https://github.com/Microsoft/TypeScript/issues/247#issuecomment-173649104, https://github.com/Microsoft/TypeScript/issues/5787, https://github.com/Microsoft/TypeScript/issues/2709, https://github.com/Microsoft/TypeScript/issues/6371#issuecomment-169597919

Problems:

  • Can not easily specify a loader extension for json or css (e.g. `json!a.json); currently every resources needs to have its own declaration
  • Problem with migrating to .ts, with modules that do not have typings, and users just want to get by

    Proposal:

  • Allow for short hand module declarations:

    declare module "foo";
    

    to be equivalent to:

    declare module "foo" {
      var _temp: any;
      export = _temp;
    }
    
  • Allow module names to have a wildcard character and match that in lookup

    declare module "json!*" {
      let json: any;
      export default json;
    }
    
    import d from "json!a/b/bar.json";
    // lookup:
    //    json!a/b/bar.json
    //    json!*
    
  • Additional the module "*" would allow for matching all unknown modules in the system, which can be used as a way to suppress all missing module errors

    declare module "*";
    
    import d from "some\unknown\module"; // no error, d is any
    
  • Report no-implicit-any errors for modules declared using the shorthand notion
    Open issue: is this reported on declaration or use sites
Committed Fixed Suggestion

Most helpful comment

Same use case here, using Webpack and would like to import HTML files via import template from './template.html'; At the moment I can work around it by using require() syntax, but that's inconsistent with the rest of the code style (and I would actually prefer forbidding require() in tslint). Also, it makes it difficult to transition from Babel since Babel has no problem with this syntax. IMHO the ability to import files other than ".ts" is essential to integration with modern build tools and can't come soon enough.

All 74 comments

Epic solution for #2709 !

Nice to see this is being worked on -- looks like an interesting solution that neatly captures all the outstanding requests.

Personally, I'd also like to see a compiler flag for the declare module "*"; use case -- it seems a little more natural, something like noExplicitAny, except for modules.

Thanks again for all the great work!

+1
Great solution! Looking forward to be able to delete all modules I created manually to keep the compiler happy

Allow for short hand module declarations:

declare module "foo";

to be equivalent to:

declare module "foo" {
    var _temp: any;
    export = _temp;
}

I think that this doesn't cover all cases, as this fails currently:

declare module "foo" {
    var _tmp: any;
    export = _tmp;
}
declare module "bar" {
    import { x } from "foo"; // Error: Module foo has no exported member x
    import y from "foo"; // Error: Module foo has no default export
}

thanks @ivogabe. the idea is you can use this module in any way you want and you just get any with no error. So the proposal should be updated to:

declare module "foo" {
    var _temp: any;
    export = _temp;
    export default _temp;
}

I like the idea, but I think 2.0 is too far for this. This can help to solve so many issues the community has with lack of typings/etc.

Thanks, this certainly address my use cases!

@amcdnl, 2.0 is the next release after what is currently being stabilised as feature complete.

This is great! (GitHub really needs an upvote button.)

Two questions:

  • Are the current proposals able to cover the following scenario?
import "xyz.less";

Can I do this?

declare module "xyz.less";
  • Will this syntax be one of the proposals?
import template: string from "template.html";

@jack4it if I understand the proposal correct:

declare module "xyz.less";

Would automagically make the import xyz from 'xyz.less' or import * as xyz from 'xyz.less' as type any. If you are just importing for side effects, like in your first statement, it is entirely up to what module loader you are using and how it is configured. Almost any module loader will require some sort of plugin (though obviously using NodeJS's registry can absolve people from having to consider this, but that doesn't make for very "portable" code).

If you wanted the last to work, you would want to declare an ambient module like this:

declare module '*.html' {
    const template: string;
    export default template;
}

And you module loader would have to ensure that it process things appropriately.

Personally, I think any more automagic behaviour would be dangerous.

Will this syntax be one of the proposals?
import template: string from "template.html";

No. 1. is it makes it much harder to update your definitions later on as now you have sprinkled template: string throughout your code base, and 2. type annotations will need to be supported on all import forms not only default imports import d from "mod" but for namespace, and property imports, and that would add yet another variant to the import syntax, and we already have plenty.

Interesting. #3691 is also related.

I'm a little bit surprised that you're planning to support declare module "*";, after your comment here about option 2. =)

Now I'm not sure whether to use that or to keep an explicit list of shorthand module declarations. Hmm.

Edit: After reading your comments in #247, I think that I'll go with the latter option, so that if I mistype a module name, it will be reported as missing as opposed to just being typed as any.

For those who seek for quick solution until 2.0 will be released: I've added line

        if (moduleName.indexOf('!')>=0) return undefined;

just after

            var resolvedModule = ts.getResolvedModule(ts.getSourceFileOfNode(location), moduleReferenceLiteral.text);

in resolveExternalModuleNameWorker function, so TS will ignore all plugin's imports.

I know it is quick-n-dirty solution, but now it allows me to perform syntax check of TS code when using SystemJS plugins.

Does the proposed solution also allow matching of modules with rooted and relative names?

I ask because all the examples given thus far only demonstrate the non-relative case.

In other words, will this:

// FILE: ambient.d.ts
declare module '*.css' {
    var r: string;
    export default r;
}

// FILE: something.ts
/// <reference path="ambient.d.ts" />
import css from './component.css';

work as expected despite the leading ./?

@mark-buer, no the current proposal does not handle that. can you elaborate on your scenario?

@mhegazy I would expect example of @mark-buer would work, because pattern '*.css' matches './component.css'.

As use-case: I am using SystemJS with TS. And I have a lot of resources loaded with SystemJS plugin in the way: import template from './component.html!text' that is valid SystemJS import.

The relative path is required, because include is done from folder, where TS-code of component is located. It would be very strange to import css/template of component using absolute paths.

@mhegazy, @olostan summed it up nicely.

Currently I'm using the same setup with SystemJS like @olostan and used a variation of his fix from here to allow using SystemJS imports without getting tsc errors: https://github.com/olee/TypeScript/commit/f55e5e7a042512d0a099b64cbb5d127d5b57a161

I think the proposed solution at the top of this issue would greatly help to solve such issues.
Has there been any update / official statement on this yet?

Same use case here, using Webpack and would like to import HTML files via import template from './template.html'; At the moment I can work around it by using require() syntax, but that's inconsistent with the rest of the code style (and I would actually prefer forbidding require() in tslint). Also, it makes it difficult to transition from Babel since Babel has no problem with this syntax. IMHO the ability to import files other than ".ts" is essential to integration with modern build tools and can't come soon enough.

I second what @mischkl said. The ability to easily load templates as string values from .html files (or .twig, .handlebars, or any other arbitrary file extension), and have them compile along with TypeScript files as an integral part of the source code is essential.

This is one of the main requirements I have that would need to be met before I would decide to use TypeScript in a large web application. Solving this in a hassle-free way would truly set TypeScript apart from the competition.

Awesome!

Thank you @kitsonk for the proposal!

Hooray!

Awesome! Out of curiosity-- which pull request(s) resolved this?

@bkotos #8939 and #8945.

was this included in any nightly build ?

@agalazis Yes. The labels Fixed and Milestone of TypeScript 2.0 indicate that it is in master.

I'm new to TypeScript; could someone explain how I can go about importing non TS files right now?

  1. Which version of Typescript should I install, since this isn't in any of the public releases?
  2. Where do I write the module declaration? (Which file, and where should I place it?)

Hi @athyuttamre. You're posting in a GitHub issue thread. These are for specific issues with TypeScript.

It sounds like you're new to the language, so my recommendation would be to start with official handbook, found on https://www.typescriptlang.org/docs/handbook/basic-types.html. If you end up stuck with a specific issue, and have a piece of code that doesn't work, then Stack Overflow is usually a good place to find help.

@athyuttamre The online handbook doesn't yet cover this, but the bleeding-edge handbook can be viewed here.
This feature is available via npm install typescript@next.
The declaration should go in a .d.ts file; the online handbook will help you there.

Thanks @andy-ms and @janaagaard75!

I'm able to import the values from my CSS file as import styles from 'Button.css', but it complains when I try to reference something like styles.btn, saying the property btn doesn't exist on type string.

This is what my module definition looks like:

// styles.d.ts
declare module "*.css" {
  const value: any;
  export default value;
}

Any clue how I can fix that? Sorry I'm turning this into a Q&A. If I can't resolve after this, I'll post separately on Stack Overflow.

When I create a simple project and use the definition you provide, I am able to get import styles from 'Button.css'; working with a type of any for styles, rather than string.
Maybe you have declared a module "Button.css" on its own elsewhere with the type string? Otherwise I am not sure why it is showing up as string for you.

Also be sure that this is a compile-time rather than a run-time error. This feature lets you declare the existence of special loading but does not actually provide it; for that you would want something like https://github.com/systemjs/plugin-css.

@andy-ms It works now! I'm using the webpack loader.

One issue I'm having is that while the compiler doesn't throw any errors, Visual Studio Code still doesn't recognize the import, and complains with this message: [ts] Cannot find module '!!css-variables!styles/constants/constants.css'. The loader css-variables is something I defined and has no errors.

Does VSCode not use the installed version of TypeScript (in this case, the nightly)?

Here is how to get vscode to use the.l nightly build: https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Nightly%20Builds.md#visual-studio-code

@mhegazy thank you!

Great solution!

@mhegazy Do you know by chance if there's a way to change the used TS version in a per-project base in Visual Studio? I'd like to switch it for Visual Studio, but changing the registry which will affect the whole system is a no-go to me.

The option is universal for VS and VSCode.

Should this work with node (commonjs) side too?

I'm using 2.0.0-dev.20160707 with declaration
declare module "*!any" { const m: any; export = m; }
and using the declaration like so
import * as yargs from "yargs!any";

This complies fine, but during runtime module.js fails with
"Cannot find module 'yargs!any'"

Messing with the declaration it seems that the compiler works fine with this (for example changing export = m to export default) but node doesn't understand "!any" directive - should compiler remove this?

Use 'declare module "yards";' instead

This is what I have been doing. Some libraries that either don't have typings or have them but they are either outdated or bad.

I was under the impression that this "wildcard module declaration" feature would remedy this situtation so I wouldn't need to maintain separate declaration file for them, just declare one wildcare module and use that when necessary.

Just like in https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript "Wildcard character in module names" the json example.

another simple workaround for now is

let template = require("./app.component.html!text");

if you so choose you could declare declare module "*"; which effectively shuts down all module errors. use with care though.

@mhegazy . Tried implementing the knowledge I have been getting from this forum. I am trying to import html files as strings in Visual Studio 2015 and am using typescript 2.0.0. I created an ambient module declaration like this

  /* import all html files as Strings */
     declare module "*.html" {
     var __html__: string;
     export default __html__;
       }

And in my imports

    import DLATemplate from '/Template/activity/ActivityDla.html'
    @Component({
        selector: 'activities-dla',
        template: DLATemplate
    })

I can now import my module without errors but when building the project I get tsc exited on code 2.
On digging further in the build output details I get the detailed version of the error

_TS6054:Build:File 'C:/PMLBundled/Phoenix/Phoenix_LMS/Phoenix.Web/Template/activity/ActivityDla.html' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'._

Did I miss something. I do not want to change my html files to a .ts extension because I will lose my intellisense and syntax highlighting in the html templates.

Maybe try setting the allowNonTsFiles compiler option?

Did I miss something. I do not want to change my html files to a .ts extension because I will lose my intellisense and syntax highlighting in the html templates.

you should not need to change your file extension.

this error should only be shown if you passed the html file on the commandline. how are you building your project? can please file a new issue and share a sample repro?

Thanks for the reply. For some strange reason this file in question had included in the tag

<TypeScriptCompile Include="Template\activity\ActivityDla.html" />

in my project file, reinforcing what you said that it was being passed somehow to the command line.
I deleted it and now my project compiles. Now my project runs but that
exported template is somehow inaccessible and I guess this is now a non
typescript issue. I have built a plunker http://plnkr.co/edit/w20K9s?p=preview to reproduce.

On Tue, Sep 6, 2016 at 1:06 PM, Mohamed Hegazy [email protected]
wrote:

Did I miss something. I do not want to change my html files to a .ts
extension because I will lose my intellisense and syntax highlighting in
the html templates.

you should not need to change your file extension.

this error should only be shown if you passed the html file on the
commandline. how are you building your project? can please file a new issue
and share a sample repro?


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/6615#issuecomment-245019178,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AMHUrb8f2xn__cG6VV4kpxossDxozYGgks5qnZ2ggaJpZM4HL7-C
.

@mhegazy When I re-export a module based on shorthand ambient module declaration then es5 generated code does not contain function __export(m) {...}. If I use the slightly longer format (that shorthand is supposed to be equivalent to) then the proper es5 code is generated.

Not sure if this is a bug or by design? Could not find more info on this.
I am currently using typescript 2.1.0-dev.20160905.

When I re-export a module based on shorthand ambient module declaration then es5 generated code does not contain function __export(m) {...}. If I use the slightly longer format (that shorthand is supposed to be equivalent to) then the proper es5 code is generated.

I can not piece together a repro from this description. please file a new issue, and provide enough details about your code, what you expect, and what does not work.

@abidmix I've the same problem.

Why not using regex for it instead or wildcard ?

What's the reasoning behind requiring the module definition stub for untyped imports, instead of just automatically treating them as untyped (or allowing for both)?

We're seeing a lot of Ionic developers have to supply a definitions.d.ts file with essentially a few lines of stub module definitions like declare module "my-es6-library" if they're using JS libraries that don't have types.

I think it would be a lot more natural and forgiving to have that functionality happen automatically behind a compiler flag, perhaps implicitAnyUntypedModules. Then, they can import any ESM module they find that doesn't have types without having to also remember (and to know) that they have to write a stub line for the module.

As a bonus, this feels like the TypeScript dream of improving _my_ code, but allowing me to interop with the entire JS ecosystem whether or not they choose to support TypeScript.

There was some discussion about this at the issue that preceded this one: https://github.com/microsoft/typescript/issues/3019#issuecomment-98851781

EDIT: I see this point has been belabored quite a bit in #2709. I wonder what it would take to revisit this decision?

For me it has always been #5787 which has been invaluable for code sets that use some sort of loader plugin. Because some have chosen to _abuse_ the feature to allow any module to be imported is a side effect of this feature.

On the idea @mlynch brought up, I have often wished for the same thing but I think there are also valuable reasons for the current design. Requiring the speedbump of just a bit of extra effort to persuade typescript to let in some untyped library helps a bit to increase the community pressure on the makers of libraries to please ship typings, or if they prefer not to have typings in their pure JS project, at least put them in the @types/* mechanism. I think that pressure is a good thing, though it is hard to say what the exact optimal amount of pressure is to maximize the fraction of popular JavaScript libraries that ship with typings in the near-to-far-future.

@kylecordes from an adoption standpoint, it's certainly compelling to have the force on library authors. I don't get the vibe from TS lang that they are intentionally doing that, so I feel like it's not worth the impact on developer experience.

Also, I wrote some thoughts on this issue last night and I hope we can see some movement towards a less strict, more JS ecosystem-friendly solution: https://medium.com/@maxlynch/the-one-thing-id-change-about-typescript-e76cc2eb4bd2#.o04vaqose

IMO smoothing the new user experience and making existing projects conversion easier is a good way to bring more developer into typescript. And more typescript users means more pression on library authors to deliver definitions.

While it is the law of unintended consequences with this feature, it is just as "bad" as the default that noImplicitAny is off by default. In fact in this case the developer has to take active steps to "game the system" to allow them to produce poor code. You have to actively instruct TypeScript to not validate modules at design time.

If you have the settings "moduleResolution": "node", "allowJs": true, and "maxNodeModuleJsDepth": 1, the compiler will be able to find some JS packages in your node_modules without needing a declaration. (This might not work if the package has an unusual "main" target.)

And we have an issue open for a better solution: #11106

I still don't understand how to properly implement a mock definition to allow the following syntax:

import { MyClass} from "my-package";

The following definition will not allow the code to compile:

declare module 'my-package' { 
    var _temp: any; 
    export = _temp; 
    export default _temp;
}

I understand the objective of forcing JS package developers to publish typings, but there should be a canonical way to make it work without it.

@mayerwin you can either define the whole module as any:

declare module "my-package";

or give it more details about the type of the export MyClass:

declare module "my-package" {
     export class MyClass {
          .....
     }
}

you can find more information about authoring declaration files at: http://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html

@mhegazy Thanks 👍, I wasn't aware of the possibility to declare the whole module as any. It works. It would deserve to be highlighted more to help people know how easily they can tap into the JS ecosystem if they make the move to TS.

you can find short-hand module declarations in:
http://www.typescriptlang.org/docs/handbook/modules.html#shorthand-ambient-modules
and in http://www.typescriptlang.org/docs/release-notes/typescript-2.0.html#shorthand-ambient-module-declarations

@mhegazy : hi , I had a question about this issue
I try like this:
in file.ts I write

/// <reference path="require.d.ts" />
import JsonInfo from "json!files.json"; 

I am not sure /// need or not

then I new a file called require.d.ts

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

and the files.json is in base dir like below

index.html
files.json
app/
   -  file.ts
   -  require.d.ts

and I get below error
image
image

but I can access the json file
image

I check the https://www.bountysource.com/issues/41187251-ambient-module-declarations-with-wildcards-giving-errors-official-docs-example-not-working
and http://www.typescriptlang.org/docs/handbook/modules.html#shorthand-ambient-modules

now I have no answer,hope your reply,tks

This feature just allows you to declare that a kind of import should work. It doesn't guarantee that it actually will work at runtime. (You're getting a 404 error, not a compile error.) That's a problem for whatever module loader you're using. (If your module loader doesn't support json! loading, it won't work.)

@andy-ms : thank you first for answer

you mean systemjs (module loader) not support?

now I just use sample of hero to test json,I think loader maybe ok

this is my config

(function (global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': 'node_modules/'
        },
        // map tells the System loader where to look for things
        map: {
            // our app is within the app folder
            app: 'app',
            // angular bundles
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
            // other libraries
            'rxjs':                      'npm:rxjs',
            'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: './app.module.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            },
            'angular-in-memory-web-api': {
                main: './index.js',
                defaultExtension: 'js'
            }
        }
    });
    "rxjs": "5.1.0",
    "systemjs": "0.20.5",
    "zone.js": "^0.6.25",
    "json-loader" : "^0.5.4"

pls help check if systemjs don't support or not or other problem?

SystemJS uses postfix plugin syntax: e.g

import JsonInfo from "files.json!node_modules/json-loader/index.js";

There has been discussion of supporting postfix and prefix notation but I do not believe it is currently supported.

See https://github.com/systemjs/systemjs/issues/1092

Note that using package configuration as in

packages: {
  app: {
    main: './app.module.js',
    defaultExtension: 'js',
    meta: {
      '*.json': {
        loader: 'my-loader'
      }
    }
  },
  ....

with no plugin specifier as in

import JsonInfo from 'files.json';

is the preferred approach

@aluanhaddad : if like

meta: {
      '*.json': {
        loader: 'my-loader'
      }
      import JsonInfo from 'files.json';

will give the error with cannot find the module "files.json"

image

and json-loader is same
image

That's because SystemJS is separate from TypeScript. So you need to declare module '*.json' too.

@andy-ms : yeah,it works(not give 404 error) , but when I print it , give undefined; when I give complex json, like { "name" : "test"}, then it will printf
image

image

file.ts

import JsonInfo from "files.json"; 
console.log(JsonInfo);

files.json

{
  id : 3
}

module

declare module '*.json' {
    const value: any;
    export default value;
}

systemjs

meta: {
            '*.json': {
                loader: 'my-loader'
                }
            }

That is invalid JSON.

I'm not sure if anyone solved this, but I was able to get it to work using @wenbaofu examples above. I just provided it valid json and used systemjs-plugin-json as the loader.

Umm, can I do something more differently?
Like

declare module "*.module.js" {
    var loader: AsyncModuleLoader
    export = loader
}

I use this type of declaration to type async module(by webpack), but not success

Was this page helpful?
0 / 5 - 0 ratings