Home > Software design >  Typescript compiles fine, but emitted JavaScript import fails
Typescript compiles fine, but emitted JavaScript import fails

Time:11-21

I'm trying to use Litepicker on a webpage through Typescript, but the emitted JS can't load the module. This is what I have:

index.html

<html>
    <body>
        <input type="text" id="picker">
    </body>
    <script type="module" src="node_modules/litepicker/dist/litepicker.js"></script>
    <script type="module" src="test.js"></script>
</html>

test.ts

import Litepicker from './node_modules/litepicker/dist/types/index';
new Litepicker({
    element: document.getElementById('picker'),
});

test.ts compiles fine, and the emitted test.js, is identical to the test.ts file.

However, when I load index.html on the browser I get the following error:

GET https://192.168.0.160/test/node_modules/litepicker/dist/types/index net::ERR_ABORTED 404 (Not Found) test.js:1

If I remove the import from test.ts like this:

declare let Litepicker: any;
new Litepicker({
    element: document.getElementById('picker'),
});

then the emmited JS runs fine on the browser and I get no errors.

So, how can I import the module on .ts and have no errors on .js?

(this is my tsconfig.json file)

{
    "compilerOptions": {
        "target": "es2020",
        "sourceMap": false,
    }
}

CodePudding user response:

tl;dr

Use a bundler (examples: Parcel, Rollup, Webpack). You can then do:

import Litepicker from 'litepicker';

(and remove the script tag for Litepicker)

Problem

In test.ts you import Litepicker from './node_modules/litepicker/dist/types/index' and with your TypeScript config it gets compiled as this:

import Litepicker from './node_modules/litepicker/dist/types/index';

The browser will then attempt to resolve that path to try to import it but there are a number of problems:

Browser path resolution

A browser will try to resolve the path literally. It will not behave like Node which makes attempts at resolving the path with various file extensions (see the pseudocode algorithm here). In other words it will try to find ./node_modules/litepicker/dist/types/index. And this file (index) doesn't exist (I assume it exists as index.d.ts given it's in a directory called types). Hence it gives you a 404 not found error.

GET https://192.168.0.160/test/node_modules/litepicker/dist/types/index net::ERR_ABORTED 404 (Not Found) test.js:1

Importing type definitions vs implementation

The second problem is you are likely importing a type definition file. I'm making this assumption because the directory the index file is in is called types. Types are useless at runtime. And the browser won't understand what to do with it. In general, you don't need to import type definitions, you just need to import implementations because TypeScript will understand how the type definitions link up with the implementation.

Solution

There might be different thoughts going on in your mind right now:

  • Now I know the browser resolves the literal path I can just change the path to point to the correct place, right? For example: import Litepicker from 'node_modules/litepicker/dist/litepicker.js'
  • import Litepicker from 'litepicker'; is a thing?

The first solution of importing the literal path is not idiomatic. It also might not work, depending on how the module is exposed. Using import Litepicker from 'litepicker'; is better understood by other developers. However you've mentioned in a comment to another answer saying to do this that it doesn't work. Let's dig into why.

The first problem should be somewhat obvious now given I've already elaborated it above: the browser won't know how to resolve it. You've also discovered this when you observed the error:

Error resolving module specifier “litepicker”. Relative module specifiers must start with “./”, “../” or “/”

As a result we'll need to help the browser understand where this module is. The first part of this solution is a module system (for example: CommonJS modules, AMD, SystemJS). This essentially augments the native module system (ES modules), providing an interface for defining modules and wiring them up. You likely won't actually implement anything yourself here because it would mean understanding the module system relatively in-depth and writing lots of boilerplate code which has a risk of errors. I would recommend relying on the second part of this solution: a bundler. A bundler will transform your modules to fit into the chosen module system (this choice may be made by the bundler). Examples of bundlers, in no particular order, include:

I won't elaborate on how to use a bundler because it depends on the bundler you choose and the bundler will have documentation on how to use it. Some bundlers may work out of the box with TypeScript (Parcel) while others (Rollup and Webpack) will require additional configuration/plugins.

CodePudding user response:

If the dependency is in your node_modules you can simply import it by:

import Litepicker from 'litepicker';

This is also in their docs that you linked

  • Related