Keith
Keith

Reputation: 155832

How to make TypeScript output valid ES6 module import statements?

All major browsers have supported ES6 modules for some time.

These differ from many of the server-side approaches in that they need to specify the exact file to import from - they can't use file discovery.

This makes sense - in Node applications or bundlers like WebPack they only really need the name of the module, and then can spend a bit of extra time discovering the specific file that holds the code. On the web that could be a lot of wasted round trips (is 'library' in library/index.js, or library/library.js, or library.js? require() doesn't care but on the web we have to).

TypeScript has ES6 modules support (set "module": "es6" in tsconfig.json) but it appears to be using a file discovery approach...

Suppose I have library.ts:

export function myFunction(...) {  ... }

Then in app.ts:

import {myFunction} from './library';
var x = myFunction(...);

However, this is unchanged when transpiles - the TS output still has the 'library' name for file discovery, which doesn't work. This throws an error because 'library' isn't found:

<script type="module" src="app.js"></script>

In order for ES6 modules to work the TS output needs to reference the specific file:

import {myFunction} from './library.js';
var x = myFunction(...);

How do I make TS output valid ES6 module import statements?

Note: I am not asking how to make a bundler join the TS output into a single file. I specifically want to load these files individually using <script type="module">

Upvotes: 29

Views: 17242

Answers (3)

Ron Newcomb
Ron Newcomb

Reputation: 3302

For a personal project I went the other way. Since I had NPM calling a shell script to copy index.html over to the /build folder, I had the shell script then mass-rename all .js files to have no extension at all.

I did have to inform IIS in "MIME Types" section that an extension-less file should have MIME type application/javascript for that particular site, but it did indeed work. No webpack, no SystemJS, nothing. Index.html just had a hard-coded

    <script type="module">
      import "./app";
    </script>

This was important because I was using a testing framework jest which did not like me putting the .js into the typescript import statements.

Upvotes: 1

Keith
Keith

Reputation: 155832

This is a bug in TypeScript, though there's some debate about whether it should be fixed.

There is a workaround: while TS won't allow you to specify a .ts file as the source of a module, it will let you specify a .js extension (and then ignore it).

So in app.ts:

import {myFunction} from './library.js';
var x = myFunction(...);

This then outputs correctly in app.js, and TS has found the import definitions and bindings correctly.

This has one advantage/gotcha to be aware/careful of: TS just ignores the .js extension and loads the rest of the path with the usual file discovery. This means that it will import library.ts, but it would also find definition files like library.d.ts or import files in a library/ folder.

That last case might be desirable if you're joining those files together into a library.js output, but to do that you're going to be looking at either lots of nested tsconfig.json files (messy) or possibly the pre-transpiled output of another library.

Upvotes: 24

Fenton
Fenton

Reputation: 251172

The compiler takes a module kind flag:

--module ES2015

And you'll also need to be targeting ECMAScript 6 / 2015...

--target ES2015

You need both the module kind and the compilation target to be ECMAScript 2015 minimum to have "zero transformation imports".

Your import statements should look half-way between your two examples:

import {myFunction} from './library';

Additional Notes

There is still clearly a lot of discussion about module resolution... there is the TC39 specification, and the WHATWG specification - plus Node is currently still file-extention-less... looks like RequireJS might live longer than we all thought... please see:

The TypeScript thread for supporting file extensions during import transpilation (i.e. will it add the file extension?).

Recommendation

Stick with a module loader, for example RequireJS or SystemJS. This also means your modules can be shared between browser and server by using UMD or System module kinds repectively.

Obviously, once the ECMAScript discussion reaches a conclusion this will need a revisit.

Upvotes: 3

Related Questions