TypeScript: files are not scoped if they don't contain at least one export or import statement

TypeScript Version: 2.5.2

Code

// a.ts
const template = document.createElement('template')
template.innerHTML = `...`
class Foo extends HTMLElement {}
customElements.define('my-foo',Foo)

// b.ts
const template = document.createElement('template')
template.innerHTML = `...`
class Bar extends HTMLElement {}
customElements.define('my-bar',Bar)

// main.ts
import './a'
import './b'

image

tsconfig:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./ts-output",
    "strict": true,
    "pretty": true,
    "moduleResolution": "node"
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

Expected behavior: every file is isolated module, variable definitions should not be leaking, as they are private if not exported

Actual behavior: will get TS error unless export or import is used within a.ts or b.ts

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 13
  • Comments: 31 (17 by maintainers)

Most upvoted comments

Wouldn’t it be natural to also treat every .ts file as a module if --isolatedModules is specified, regardless of whether the file contains import/export or not?

I’d love to see a tsconfig.json option that compiles all of my .ts files inside of rootDir with export {}; implicitly.

Having to remember to add an empty export on files which do not export/import is one of the worst experiences with using TS.

There’s already a syntactic directive for turning something into a module when it has no imports/exports:

export { }

I think it just comes down to that this is a frustrating workflow thing that no one seems to want to fix.

I can’t expect NodeJS to make non-module files share global scope. That would be a huge breaking change.

I can’t expect TC39 to make a spec fix for this because they’re more concerned about browser usage (where this functionality makes a bit more sense)

It seems everyone is passing the blame.

The compelling reason in my mind is why should TypeScript come up with a unique solution

Because Typescript has a compiler which ships with many options, and it’d be appreciated.

There is a proposal to add a pragma "use module"; to eliminate this ambiguity. Our recommendation is to use export {}; to your file to force treating it as a module.

Use export {} to force a file to be treated as a module. Giving the --isolatedModules flag will also ensure that all your files are modules, by making it a compilation error if it isn’t one. (Ambient declaration files are unaffected)

😊 that is a heck of a lot better than my export let undefined!

@aluanhaddad that’s not true. Files are modules if they are imported from a module, or loaded with script/type=module, and have a JavaScript mine type, that’s all.

TypeScript should follow they spec there.

There’s already a syntactic directive for turning something into a module when it has no imports/exports:

export { }

There is a proposal to add a pragma "use module"; to eliminate this ambiguity. Our recommendation is to use export {}; to your file to force treating it as a module.

That makes sense, to follow the TC39 workaround until the pragma reaches Stage 3.

Browser and Node host environments specify whether something is a script or a module out-of-band (type="module", "type": module"). It’s not correct to consider export {}; a TC39-given directive to make something a module when it’s supposed to throw a syntax error when used in scripts; using it for inference is entirely a TypeScript invention. It’s clear that tsc should have a compiler option to treat all or some source files as modules without requiring any particular syntax usage within the files.

But my point is, a poor module declaration file shouldn’t be able to pollute the namespace of all my modules and then produce false compilation successes.

There’s no distinguishing characteristic between a “poor module” and a “good global” file. If you want to ensure that a file is a module, there’s already syntax for it, export { }.

I guess I should make another GitHub issue for this?

This isn’t something we’d do. There’s no ES6 construct for “import from global” and we’re not going to add something that looks like it’s a module import but isn’t.

How should TypeScript know when an arbitrary file is a module vs a global? If the language isn’t aware of any importing modules for that file, then it may not do the correct thing.

Right now everything is global by default which is definitely the least optimal state of the world, but I don’t think this is a straightforward problem given where we are now.

That is how modules are specified in ECMAScript.

@kitsonk I’m a little confused here. Correct me if I’m wrong, but for an environment like NodeJS, files are always modules (or at least they behave that way!).

// a.ts
const x = 1;
// b.ts
require("./a");
console.log(x);

node out/b.js results in ReferenceError: x is not defined

You could say that this is NodeJS not abiding by the JS spec, but TS has added options for stuff like this in the past. i.e. "moduleResolution": "node",

I don’t really understand the reasons against adding a tsconfig.json option to assume files are modules.

Right now everything is global by default which is definitely the least optimal state of the world, but I don’t think this is a straightforward problem given where we are now.

Even if the compiler “modularlized” anything that was imported, that may not reflect the intent of the code, as often code written without any imports or exports is intended to be loaded in the global namespace and would be a breaking change. We have often used import 'foo'; exactly for its global side-effects, to load a 3rd party library or polyfill.

I can understand though that there can be modular code which can, as the example above, interact with the DOM but not be safe to load into the global namespace and still not have any imports or exports but that seems to be an edge case.

Could a triple slash directive to force a module be a solution? Similar to the way that some of the AMD information can be specified.