Skip to content

Path resolution for URI style import #35163

Description

There are some use cases that use tsc to emit ESModule that runs in the browser directly. Currently, TypeScript is not friendly to browser style import environment like browser and deno.

There are some main problems when using TypeScript in those environments:

1. Import .ts, .tsx file is a type error

Related issues: #30076

This happens in the deno. Deno will not try to resolve implicit extension name when handling imports.

import "./file";

This import is invalid for deno and browser.

In browser we have to write import "./file.js"; which is valid in the type checker.

In deno we have to write import "./file.ts"; which is invalid in the type checker. TypeScript reports An import path cannot end with a '.ts' extension. Consider importing './file' instead

#35148 introduce a change that importing a .ts or a .tsx file is no longer a type error. Importing .d.ts is still a type error.

2. Emitted code does not contain the .js extension name

Related issues: #16577, #30076

When tsc is used to emit ESModule for browsers, the extname is not added. Now one can write import './file.js' to refer to file.ts, that behavior is strange in the semantic.

#35148 introduce a new compilerOption called --emitExtension. With this option, TypeScript will replace the .ts or .tsx extension name for relative imports.

import "./file";
import "./file2.ts";
import "./file3.tsx";
// with --emitExtension .mjs
// result to
import "./file";
import "./file2.mjs";
import "./file3.mjs";

3. Can not emit file with .mjs extension name

Related issues: #30076, #18442

In Node.js, there is a discussion about how to migrate to ES Module, a solution requires all ES Module files have to have a .mjs extname. There is no way to emit such code by tsc now.

#35148 introduce a new compilerOption called --emitExtension. With this option, TypeScript will emit the code with the specified extension name.

There are lots of different solutions that requires different extension name for js file like .es, .mjs, .es.js so TypeScript maybe should not restrict the possible value of --emitExtension with a enum.

--emitExtension must start with . and can not be .d.ts

when using --jsx preserve, --emitExtension must be .jsx

src
    index.ts
    index2.ts
    index3.ts
# with --emitExtension .mjs
# will emit
dist
    index.mjs
    index2.mjs
    index3.mjs

4. Can not handle the type resolution for URI import finely

Related issues: #28985, #19942

In browser and deno, import from a "package" is different from the node style.

// browser
import lodash from "https://unpkg.com/[email protected]/lodash.js";

// deno
import { serve } from "https://deno.land/[email protected]/http/server.ts";

Currently there is no way to let TypeScript automatically map the URI to another place like @types/* or $DENO_DIR/deps/https/deno.land/*

The current path can map a simple pattern of import module specifier to another place, but in the URI style import, a more flexible way to map the URI is required.

Proposal

(maybe add a new moduleResolution: browser)
Add a new uriPaths that allows to map from a RegExp to a local path. It will NOT effect the emitted code. Just a way to find the type definition for those URIs.
Example:

{
  "compilerOptions": {
    "uriPaths": {
      "https://unpkg.com/(.+?)@.+?/.+": "./node_modules/@types/$1",
      "https://deno.land/(.+?)@v.+?/(.+?)/(.+)": "$DENO_DIR/deps/https/deno.land/*",
      "std:(.+)": "./node_modules/builtin-module-types/$1"
    }
  }
}

This rule map https://unpkg.com/[email protected]/lodash.js to @types/lodash-es

Map https://deno.land/[email protected]/http/server.ts to $DENO_DIR/deps/https/deno.land/std/http/server.ts.

$DENO_DIR is an environment variable.
By default, on Windows, it's ~\AppData\Local\deno\deps\https\deno.land\std\http\server.ts.
By default on Linux, it is ~/.deno/deps/https/deno.land/std/http/server.ts.

5. Should treat folder import (import './src' => import './src/index.ts') as type error in some environments

*: Folder import is supported only in moduleResolution: node

The folder import is only supported in Node.js and some bundle tool, browsers and deno doesn't support it.

Proposal

Add a flag --noFolderImport to ban the folder import.

6. Should treat import without extension name (import './src' => import './src.ts') as type error in some environments

The implicit extension name is only supported in Node.js and some bundle tool, browsers and deno doesn't support it.

Proposal

Add a flag --noImplicitExtensionName to ban the implicit import.


There are 6 problems when tsc is used with browser native ESModule or deno. The first one to the third one is fixed by #35148.

I have interest to do the 4 to 6. But I should make sure TypeScript team will accept those.

Activity

  1. xlaywan commented on Nov 23, 2019

    @xlaywan

    Also there is no way to configure auto-import to always prepend '/' to absolute path.

    This import(which VSCode produces) is not supported in browsers:
    import {utils} from 'utils.js'

    this is supported:
    import {utils} from '/utils.js'

  2. Jack-Works commented on Nov 24, 2019

    @Jack-Works
    ContributorAuthor

    Also there is no way to configure auto-import to always prepend '/' to absolute path.

    This import(which VSCode produces) is not supported in browsers:
    import {utils} from 'utils.js'

    this is supported:
    import {utils} from '/utils.js'

    Relative import may be enough

  3. fatcerberus commented on Nov 27, 2019

    @fatcerberus

    .mjs is a second-class citizen now. Node.js 13.2 just released with unflagged module support and it's possible to enable ESM package-wide by using "type": "module" in its package.json. No need for .mjs.

    But that said... extensionless imports are a problem. That issue used to just be limited to the browser (and deno), but the default mode in Node 13.2 turns out to be to require the extension for all file-based imports. --emitExtension looks like just what the doctor ordered for that, but it should probably also add the extension for imports without one, too. Otherwise life is about to get a lot harder for TS devs once ESM in Node starts really taking off.

    https://nodejs.org/api/esm.html#esm_customizing_esm_specifier_resolution_algorithm

  4. MicahZoltu commented on Nov 27, 2019

    @MicahZoltu

    I think the default behavior of TSC should be to emit valid JavaScript. At the moment, especially with the NodeJS 13.2 change, TypeScript will emit invalid JavaScript by default.

    // valid typescript
    import { Foo } from './foo'
    new Foo()
    // emitted javascript
    import { Foo } from './foo'; // invalid in browser and NodeJS!
    new Foo();
    
    // invalid typescript
    import { Foo } from './foo.ts' // invalid in TS
    new Foo()
    // emitted javascript
    import { Foo } from './foo.ts'; // invalid in browser and NodeJS!
    new Foo();

    If the user is targeting ES modules, an extension should be included by default. Otherwise, the default behavior of TSC will be to generate invalid JS which goes against the ethos of TypeScript.

  5. andrewbranch commented on Dec 11, 2019

    @andrewbranch
    Member

    Relevant reading: #35589

  6. changed the title [-]Path resolution for browser style import[/-] [+]Path resolution for URI style import[/+] on Dec 18, 2019
  7. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions