Global imports

In C#, when we write using System; in many files, we can reduce its repetition by writing it as global using System; in one file, and then all files have it.

I see myself constantly writing import { camelize } from "base" and many more imports.
It would be a great help not to repeat it repeatedly.

The main reason for this is refactoring. When the name of the imported module changes, or the barrel name changes, we should reflect those changes in many places.

Using global imports, we can reduce that refactoring cost, make our codes one step cleaner, and reduce the boilerplate.

And AMAIK, this won't cause problems in tree-shaking, because that module is already imported once and would be part of the final tree.

The proposed syntax is:

global import { camelize } from "base"

This means that all files will get this import.

1 Like

That would erase the entire benefit of modules - explicitness. That you have to write imports repeatedly is a good thing - code is written a few times and read many many times, so always optimize for reading, not for writing.

1 Like

Import Maps or package.json aliases help, they provide a layer of indirection so a rename only has to be updated in one place

1 Like

JS has global variables already. They're a bit cumbersome to create in module code, but that is on purpose - they are usually not a good idea. In your case, that would be

import { camelize } from "base";
globalThis.camelize = camelize;

Or maybe you even want

import * as base from "base";
Object.assign(globalThis, base);
// or
Object.assign(globalThis, await import("base"));
3 Likes

@bergus that's an interesting approach. The only downside is boilerplate. You have to prefix everything with "globalThis". For example globalThis.camelize('some words').

@ljharb some functions are truly global. They are pure functions and they have no side effects. They can be considered first-class citizens of the language itself. Imagine that JS had the camelize method built into it.

It's not a good practice to write import { get } from "base" a thousand times. We have more than 2 thousand files. Most of those files need to get something. Wouldn't it be nicer if we could remove that one boilerplate, repetitive code from those files?

The point is, that C# did not have this feature before. When they created it, we risked to use it. But we fell it love with it very soon.

I argue that reducing boilerplate is almost always a good thing. If something is used all over place, it's better to somehow reduce its boilerplate.

@aclaymore I didn't know about import maps. But when I searched it, it seemed that it was only for the browser and should be included inside a <script> tag. My proposal is more related to Node and server JS.

It is, in fact, good practice to write the import a thousand times if you need it a thousand times.

It's not boilerplate if it's providing information - someone reading just one of those 2 thousand files shouldn't have to magically know what globals some random dev at your company installed.

1 Like

Wait, but why would you have to write globalThis.camelize everywhere? The point of globalThis is that it is exactly whatever object is used to look up a name that isn't in any lexical scope. This is exactly why I can write fetch() instead of globalThis.fetch(). It works because fetch === globalThis.fetch

1 Like

@ljharb with respect I disagree. We do have some global functions like setTimeout or fetch.
Imagine being forced to import them whenever you need them. Now I need to add a couple of these methods for my team only. After all, a teammate doesn't just pop out of nowhere and start coding. It's a simple teaching that:

we do have some useful functions that are globally available, like camelize, pascalize, get, post, ...

That's not a hard thing to learn. Does not increase the learning curve, does not reduce the code readability and it valid for a team as a policy.

@conartist6, so you mean that if I add them to the globalThis then I will be able to call them directly?

Yes, that would be much better in many ways, and there have been many attempts to propose "standard modules" to force that exact requirement.

Your standpoint is that we should make every file as self-contained as possible. And by writing every import, we can achieve that.

Someone else might think that we should make every dependency as weak and abstract as possible and inject everything dynamically.

Someone else might think we should reduce costs because every line of code written means a developer should be paid somewhere.

etc. etc.

The point is, while JS goes towards more imports, C# has gone towards less imports. Which one is correct? You can't tell me that engineers at Microsoft are dumb people. And of course, engineers behind JS are not dumb too. So what's going on here? Two expert teams going in exactly opposite directions?

The point is that it depends. I'm 100% comfortable with having less boilerplate code and more global imports. We have done that in codebases with more than 200 thousand lines of code (C# & Python), and we have been OK with it. No problem.

What I propose here, is a possibility. That possibility can be ignored by teams who prefer self-contained-ness and could be chosen by teams who prefer less boilerplate.

Programming languages are like a collection of different patterns. What works well in one language does not automatically work in another. If one recipe uses pineapples, that doesn't mean someone else is wrong to say that there are issues with using pineapples in a different recipe.

C# is a statically compiled language, where all references can be resolved statically during a compilation phase. While many JavaScript engines also have a compilation phase, this is more of an implementation detail - the observable semantics of the language are that it is evaluated 'on the fly'.

For C# the global modifier on using only impacts the files within that compilation unit (e.g. the project) rather than all of the code being run (e.g. the whole solution).

This is why there is more of a risk to add globals in JS, they will all be available and could clash with libraries.

I have certainly worked on projects that have added some global utilities, usually for debugging purposes, and to reduce risk they name been given names that include part of the company name to reduce a clash. e.g. acmePrintState() instead of print().

As others have said, if you do want to do this no new syntax is required. As long as the global hasn't been sealed then code can add new functions and they will be available everywhere (within that same realm).

// globals.js
globalThis.foo = () => console.log("hello from foo");
// main.js
foo();
$> node -r globals.js main.js
'hello from foo'
2 Likes

yes

My experience with implicit imports is not too good. I’m used to use React without importing it when I use Next.js, for instance, and I’m not sure it is a good idea in current JavaScript or Node.js world unless the concept of “project” is a part of the language.

Right now JavaScript modules are considered as independent pieces of code, so a declaration or import in one of them “should not” affect others. That’s why global variables are so poorly used in JavaScript world. Even if globalThis is there for you.

If we had a kind of project manifest or root settings at language level (like project.json could some day be), then we could specify a global things file there or something. Like now we have the entry point for Node.js. The important thing, in my opinion, is to have a clear place to go when you need to know what is global.

@aclaymore Another problem with imports in JS is that the path is very fragile. Of course, we can create aliases and barrels (which are hard to create neatly in Node.js). But consider these imports:

import { clientError } from "../../Core/Error.js"
import { repo } from "../../DataAccess/Repository.js"
import { modifyItem } from "./ModifyItem.js"

Move Error.js from Core directory to another directory and the entire codebase fails. Maybe VS Code can refactor this change in one project. But it fails for multi-project companies. We have an SPL and it's used for more than 100 projects per year. We can't open everything in VS Code. So, we open the Core and develop. And we fear changing paths.

To me this still sounds like a problem still better solved by improving the tooling instead of changing the language.

Is there an open issue for the VSCode issue? I'd like to read more about that.

Is there a reason you don't collect all those imports in one file, and then have a single import * from "globals" in all other files?

Did you mean import * from "globals", as a sort of syntax that automatically spills the contents of the globals module into the current namespace? That syntax does not exist.

Or were you trying to write import * as globals from "globals"?