I've been working with Typescipt for a few months, and this aspect has been annoying me over the course of several projects already. Suppose I have the following project structure:
project
+- app
| +- component1
| | +- component1.ts
| +- component2
| | +- component2.ts
| +- utils
| +- utils.ts
+- tsconfig.json
Now, suppose that utils.ts file contains a lot of helper classes (e.g. data structures) and maybe even global functions, and almost every other file in project uses at least some part of it. How would you use it with a minimum amount of boilerplate code?
Using import statement
// utils.ts
export function foo() { return 1; }
// component1.ts
import { foo, bar, baz, ...(a long list here) } from '../utils/utils'
let x = foo(); // looks fine
Pros: imported members are simple to use
Cons: necessity to maintain long import list in almost every other file
Another option is to import everything at once:
// component1.ts
import * as utils from '../utils/utils' // I have to remember to put this line on top of every other file, but it's better than the option above
let x = utils.foo(); // unavoidable prefixes all over the code
Pros: import statement is generic and can be copypasted to every file only changing the relative path (although I'd be happier if there would be a way to do so implicitly project-wide)
Cons: namespace prefixes for simple helper functions all over the code. For some reason Typescript doesn't allow to use import *
without explicit namespace.
Using triple-slash directive
// utils.ts
function foo() { return 1; } // no export keyword
// component1.ts
/// <reference path="../utils/utils.ts" />
let x = foo();
This looks much better even though I still have to specify a reference with relative path on top of every file. However, it breaks completely for me when I try to bundle my app with webpack - utils.js just doesn't get bundled along with the rest of the app (I guess this is a topic for another SO question).
What I would really like is the ability to use my code in utils.ts like I use global library classes - Map
, Set
, HTMLElement
, Promise
, etc., without any imports or prefixes, is there any way to achieve this?
P.S. Notice that I've long abandoned the good programming principle of actually splitting my utils.ts into multiple files (one per class/global function), as it would greatly exacerbate the problem above. I'm actually considering using a single file for the whole project already.
When you do /// <reference path="../utils/utils.ts" />
, what you're telling the Typescript compiler is "everything in the utils.ts module is on the global scope". Typescript won't do anything to actually make that happen, you're just telling TS that it's done, and it believes you. (For example, if you loaded a library with a <script src="..">
tag in your HTML to load a library globally, you'd use this annotation to tell Typescript about it.)
If you want to go this route, you'll need the annotation to tell typescript that utils are in the global scope, but you'll also need to actually add logic somewhere to make that happen. Webpack has a number of ways of defining global variables, but one option would be to use global
, with code like this somewhere at the start of your app:
import * as utils from "../path/to/utils";
for(const key in utils) {
global[key] = utils[key];
}
Now all your utils are on the global scope, in reality, and the reference path annotation informs Typescript of that, so that Typescript is happy.
But, honestly, I can't say I'd recommend doing this in practice. Polluting the global scope is a pretty classic anti-pattern, and generally makes programs tricky to follow. Anyone reading your code is just going to see "magical" functions like foo
that seemingly appear out of nowhere, with no indication of where that function came from. And, like all additions to the global scope, it opens the door for name-collisions.
Personally, I recommend just doing standard imports: personally, I lean towards import { foo } from "utils/utils"
, though sometimes utils.foo
is easier to grok (e.g. if there's a naming collision between some other foo
in the module I'm importing into).
With a bit of config for TS and webpack, you can write your imports like
import * as utils from 'utils/utils'
Instead of:
import * as utils from '../../utils/utils'
The relevant bits of config are "include"
in your tsconfig, and "resolve.modules"
inside your webpack config. (Or resolve.root
if you're using webpack 1.0) In your case, you'd want to add "app" to both of those lists, to indicate that "app" is the root of your source files.
With consistent import paths, if you've got a lot of frequently used imports, you could create a code snippet for them: a bit of code that can be quickly pasted by typing a particular shortcut phrase. (They're called snippets in VSCode, Atom, and SublimeText, I assume other editors have similar functionality)
For your hypothetical math-heavy application (from the comments on the question), you might create a snippet for
import Vector from "path/to/Vector";
import Matrix from "path/to/Matrix";
import Tensor from "path/to/Tensor";
to quickly import all of those things when creating a new file.
You can type foo
and hit enter, and it'll automatically add the import for foo
at the top of your file. I hate to shill for a particular editor, but if you're annoyed at the drudgery of imports, it really is pretty helpful.
If you are using a namespace import, maybe just pick a shorter one: u.foo
is only a tiny bit longer than just foo
, but it's a lot less magical than monkeying with the global scope.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With