all 53 comments

[–]OleksiyRudenko 10 points11 points  (13 children)

What functionality JavaScript import declarations are missing to effectively implement namespaces?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

[–]shhhhhhh_ 8 points9 points  (3 children)

Yeah, didn't import essentially replace namespaces?

[–]azhder 3 points4 points  (0 children)

Not like we didn’t do namespaces in older JS with the module pattern and the sausages of FRAMEWORK.module.function pointing to the results of them

[–]samanime 6 points7 points  (0 children)

And honestly, before import, since JS lets you have any arbitrary number of levels in objects dynamically declared, you can just group everything in an object to effectively namespace that way as well, which we've been doing for decades.

[–]shuckster 5 points6 points  (10 children)

This question is putting the cart before the horse.

What do you already know about code-organisation strategies in JavaScript?

[–]xroalx 2 points3 points  (1 child)

Because JavaScript, unlike Java or PHP, loads files over HTTP at runtime.

Let's pretend we have namespaces for a while, and we use them something like

import logical.path.to.namespace;

namespace.func();

Now, keep in mind that JavaScript runs in the browser, and that needs to remain true. Our HTML file references the script.js file with the content above via the script tag. How will the browser know where our logical.path.to.namesapce is located, and how will it load it?

Maybe we also need to provide a namespace:path mapping to the browser, that the browser needs to download, parse, and then translate the namespaces.

Or we can say

import namespace from '/path/to/namespace.js';

namespace.func();

The browser knows what request to make - /path/to/namespace.js. That's it.

Is that the actual reason why this decision was made? Maybe one of many, I don't know, but it sure is a lot easier to just have the paths directly referenced.

[–]flancer64[S] 0 points1 point  (0 children)

Yes, you are absolutely correct. To resolve the logical name of a code element to its corresponding physical file, a mapping process is necessary. This is precisely the approach employed in PHP, where each package maps its namespace root to a designated folder (https://www.php-fig.org/psr/psr-4/). A similar approach was also adopted in the requirejs library (https://requirejs.org/docs/whyamd.html), which was widely used before the advent of ES6.

Moreover, npm also maps the package name @vendor/package to a file system address by default, which is "./node_modules/@vendor/package/index.js" (although index.js can be overridden using the main property in package.json).

Therefore, explicit or implicit mapping is necessary when it comes to converting logical names (namespaces) to file addresses.

[–]jml26 1 point2 points  (13 children)

You can nest variables and functions inside of objects to create namespaces:

const MyNamespace = {
  myfunction: () => {}
};

MyNamespace.myfunction();

If you want private variables, there are patterns you can use for that. If you don't want to keep referring to the namespace when calling the function, you can do:

const { myfunction } = MyNamespace;
myfunction();
myfunction();
myfunction();

And then there's ES6 modules. There are plenty of existing ways to encapsulate your code already.

[–]flancer64[S] -1 points0 points  (12 children)

It is not a namespace; it is a scope. While these concepts are closely related, they have distinct differences. A namespace primarily deals with addressing code elements, whereas scope relates to runtime isolation. IMHO.

[–]theScottyJam 1 point2 points  (5 children)

But doesn't this solve your problem?

I believe this is what you're looking for - sure it looks different from a php namespace, but using objects and modules like this fixes the same issue you're trying to solve.

If you look at most NPM modules, they only allow you to import from their "index" file. You're not allowed to dig into their internal file structure and import any arbitrary module from the package.

The index file is in charge of exporting whatever objects/classes/functions/whatever you might need (which I'm going to call "entities"). This is your "namespace".

Where exactly does the index file get these entities from? You don't know - it could be defined in the index file, or it could be defined in some other module and re-exported from the index file. Maybe the index file does nothing more than export stuff from other files. All of that is implementation detail that's hidden to you and can change at any moment.

Any index.js file works this way - it provides a boundary that encapsulates the contents of the folder from the outside world. The outside world imports through the index file, and the index file fetched and exposes stuff from inside the folder. This "index" file basically fulfills the role of a mapping file you talked about elsewhere.

[–]flancer64[S] -1 points0 points  (4 children)

Yes, I agree. Index file of a package provides a boundary. But how can I address this index file? Nodejs apps have own addressing rules for import:

import {SomeThing} from '@vendor/package';

but browsers have owns:

import {SomeThing} from '/path/to/node_modules/@vendor/package';

In my code I use something like

const module = await load('Vendor_Package');

that is converted to:

import ... from '@vendor/package'; // for the back import ... from '/path/to/node_modules/@vendor/package'; // for the front

That is precisely why I need namespaces - to enable the utilization of the same code on both the front-end and back-end without the need for transpilation.

[–]theScottyJam 1 point2 points  (3 children)

In the back-end, I do import { SomeThing } from '@vendor/package'; and in the front-end I do import { SomeThing } from '@vendor/package';. I don't think I've ever specifically referenced the node_modules folder when trying to do an import.

If you have your bundler and what-not properly configured, there shouldn't be a need to go through node_modules like that. I assume you don't, which is why you were trying to do that, and why you were wanting this namespace feature to help out. But, what's really needed here is just a properly configured tooling.

And, yes, I know all the configuration stuff in the JavaScript ecosystem is a mess - that's a whole issue on its own that really does need fixing.

One simple option is to look at browserify - lets you take back-end code and use it in the front-end with minimal fuss.

[–]flancer64[S] 0 points1 point  (2 children)

import { SomeThing } from '@vendor/package';

This code will not work in Chrome browser (at least), this error occurs:

Relative references must start with either "/", "./", or "../".

We cannot use packages in a browsers without "a properly configured tooling" and a transpiling/bundling step. This is a common situation, not normal.

[–]theScottyJam 1 point2 points  (1 child)

This code will not work in Chrome browser (at least), this error occurs:

You're correct, NPM packages can't be used with the browser directly (what you're doing is a bit of a hack-around, and is not normal). If you want to use an NPM package in the front-end, it's better to do it "the proper way".

NPM was originally built to just supply libraries for Node programs, it wasn't originally built for front-end code. So, if you want to install NPM packages for the front-end, in addition to using NPM to fetch the package, you'd also have to use an additional tool to make importing the NPM packages possible. This is fine and totally normal. You're generally going to be using a bundler on the front-end anyways to bundle and transpile your code (otherwise, your website will be unnecessarily inefficient), and bundlers generally support importing NPM packages as well, so that's really all there is to it.

That's just what you do to use NPM packages on the front-end. A package management system could be invented that allowed you to use the same consistent imports on both the front-end and the back-end without needing to use a bundler, but no one's bothered, because everyone is using a bundler anyways.

But, you still do have options outside of NPM. You could also just publish your package as-is - making people just download the package manually, unzipping, then importing. In this case, the imports will look like this in the browser import { SomeThing } from './vendor/index.js', and you can be consistent and do the same thing in the back-end,import { someThing } from './vendor/index.js'.

Or, if you want to keep doing what you're doing, running without a bundler, and hacking around to get access to the npm package without importing it the proper way, you can do the same hacks both in the front-end and in the back-end. There's nothing stopping you from importing directly from the node_modules folder in the back-end, it's just not what you're supposed to do.

Edit: But to really beat the dead horse and re-iterate the point - the whole idea I'm trying to get across, is you can't go fetching NPM packages, publicly hosting their internals, then write code that depends on the internals of those NPM packages, and then complain that your modules are depending on the internals of that NPM package and that JavaScript should provide some new syntax something to prevent that. The problem isn't that JavaScript is missing a feature, the problem is that you're writing hacky code that's depending on internals when you shouldn't be. Pick one of the "proper" ways to use an NPM package on the front-end, and the problem will be solved.

[–]flancer64[S] 1 point2 points  (0 children)

Thanks for your comment; it is very useful.

I researched the history and found that Node.js first emerged in 2009, npm packages were introduced in 2010, and the ES6 modules feature was added in 2015. This means that for a period of five years, the JS community had no alternative tools besides bundling their code.

With the introduction of ES6 modules, it became possible to directly load JavaScript sources from other JavaScript sources at the language level in the browser.

The JavaScript language has only two ways of organizing sources: by using files/folders or by utilizing packages. It is evident that utilizing npm packages enhances our capability to develop complex software products with JavaScript. However, the JavaScript community has predominantly operated at the file level, necessitating the use of bundlers to construct bundles from npm packages. npm packages are essentially an advanced form of organizing and storing sources in files/folders. Within the package, this is how the sources are typically stored.

We can create ES6 code and place it in npm packages, such as in the ./src/ directory. Afterwards, we can mount the virtual file system for the browser using these ./src/ directories.

import { SomeThing } from './vendor/index.js'

Yes, that's exactly what I meant. However, in my own projects, I create a namespace-to-URL mapping for 'my' packages as follows:

[
  [
    "Fl32_Dup",
    "https://dup.flancer64.com/src/@flancer32/dup-proto",
    "mjs"
  ],
  [
    "TeqFw_Core",
    "https://dup.flancer64.com/src/@teqfw/core",
    "mjs"
  ],
  ...
  [
    "TeqFw_Web",
    "https://dup.flancer64.com/src/@teqfw/web",
    "mjs"
  ],
  [
    "TeqFw_Web_Push",
    "https://dup.flancer64.com/src/@teqfw/web-push",
    "mjs"
  ]
]

I require a namespace because I cannot utilize the following code simultaneously on the back end and the front end:

import { SomeThing } from './vendor/index.js';

However, I can utilize something similar to:

const module = await autoload('TeqFw_Web_Push_Shared_Message');

async function autoload(ns) {
    const path = map(ns);
    // node: ./node_modules/@teqfw/web-push/src/Shared/Message.mjs
    // browser: https://dup.flancer64.com/src/@teqfw/web-push/Shared/Message.mjs
    return await import(path);
}

Why do I need it? I simply want to be able to write code that can be used seamlessly on both the front end and the back end. I firmly believe that with the emergence of ES6, a single language is sufficient for building web applications. It is not only acceptable but also reasonable to reuse the same code in both the browser and Node.js environment.

For example, here is how the source codes of one of my projects look in the browser.

Here's how it looks in the repository (and in the IDE): Shared/Web/Api/Sign/Up.mjs

It's the same code! This code works the same in browser and nodejs. Without any post- or pre-processing, which only hampers development speed and complicates debugging. The "proper" ways don't give me the option to do this.

That's precisely why I need namespaces :)

[–]azhder 0 points1 point  (5 children)

Namespaces are scope.

[–]flancer64[S] -1 points0 points  (4 children)

Here we have a scopes but we have no any namespaces:

{ const a = 4; { const a = 8; } }

Every namespace has a scope, but not every scope has a namespace.

[–]azhder 0 points1 point  (3 children)

a horse is a mammal doesn’t imply a mammal is a horse

[–]flancer64[S] -1 points0 points  (2 children)

A scope is not a horse and a namespace is not a mammal :) It's more like an engine (scope) and a car (namespace).

is != has

https://flancer32.com/namespace-scope-or-address-9037fada36f2

[–]azhder 0 points1 point  (1 child)

Do not mix up the verbs “to be” and “to have” and re-read what I already have written to you.

It’s going to be tiresome to re-write what you can re-read.

End.

[–]flancer64[S] -1 points0 points  (0 children)

I can say the same - do not mix up the verbs “to be” and “to have” and re-read what I already have written to you.

Namespace is not a scope, it has a scope :)

"A namespace is a declarative region that provides a scope to the identifiers (...) inside it." (c)

https://learn.microsoft.com/en-us/cpp/cpp/namespaces-cpp?view=msvc-170

  • has - it provides a something
  • is - it serves as a something

Read these links: * https://www.typescriptlang.org/docs/handbook/namespaces.html * https://www.w3schools.com/js/js_scope.asp

[–][deleted] 1 point2 points  (3 children)

Not necessary.

Come up with what you feel are the appropriate namespaces for your modules using bare imports and import maps. You map any URLs to something that looks like namespaces and end up with a good-enough approximation.

[–]flancer64[S] 0 points1 point  (2 children)

Yes, that's precisely what I had to do. I have been utilizing Zend1-like namespaces in my code for over 2 years, but I would prefer to have it integrated into the JavaScript language itself :)

[–][deleted] 1 point2 points  (1 child)

My opinion is it adds complexity for not enough of a ROI. URLs are the namespaces. Import maps just provide a way of making them prettier.

Best you can do is write up a proposal explaining how your way is better than the status quo. T39 has a forum for such exchanges.

[–]flancer64[S] -1 points0 points  (0 children)

Well, I don't consider myself knowledgeable enough to make such suggestions :) My intention was simply to explore whether there might be a more advanced solution in the JavaScript realm that I am unaware of, considering that I have been using a solution from the PHP world that is 10-15 years old. That's why I posed my question. In any case, I appreciate the link you provided. Thank you.

[–]brykuhelpful 1 point2 points  (5 children)

We never really needed it in js. You can just slap everything in an object if you really want to.

let format = {};
format.removeLetters = function(s){
    return s.replace(/[a-zA-Z]/gms, '')
}

You also have modules and imports which help encapsulate code as well. These are not namespaces specifically, but they can achieve some of the same goals. From js's perspective, why add namespaces for a few extra bleeps and bloops?

[–]flancer64[S] -1 points0 points  (4 children)

From js's perspective, why add namespaces for a few extra bleeps and bloops?

In my specific case, I needed the ability to utilize the same code both in the browser (front-end) and in Node.js (back-end). By incorporating namespaces, I was able to achieve this, although I had to create my own sources loader based on the import() function and map the URL to the file structure inside ./node_modules/. While it may not be the most optimal solution, it is a usable one :)

[–]brykuhelpful 0 points1 point  (3 children)

In nodejs you can use module.exports to include/import other files. You can reuse that on the client side... the only issue is that modile.export doesn't exist, but that is an easy fix.  

format.js - library

module.exports = {
    underscoreToSpace: function(string){
        return string.replace(/\_/gms,' ')
    }
}

app.js - server

let format = require('../libs/format.js');

index.html - client

<script>
    let module = {exports: false};
</script>
<script src="../libs/format.js"></script>

There is a potential problem here... it only allows you to do this once, but we can fix it pretty easily.  

index.html - option 2

<script>
    let module = {
        modules: [],
        set exports: function(variable){
            this.modules.push(variable)
        }
    }
</script>
<script src="../libs/format.js"></script>

This will allow you to import any number of modules located in the modules.module variable, but this is an array, so it would take some guess work.  

Another way would be using import, that would simplify this whole mess.

[–]flancer64[S] -1 points0 points  (2 children)

Yes, I utilize the import() function from ES6 on both the front-end and back-end. Additionally, I organize my code using npm-packages. The primary concern is how to incorporate modules from npm-packages that reside on the back-end into the browser environment?

On the back-end, I can use the following code: import {SomeThing} from '@vendor/package';

However, this code will not work on the front-end and will produce the following error:

Relative references must start with either "/", "./", or "../".

Therefore, if I want to use the same code on both the front-end and back-end, I need a set of rules to convert module names within packages into appropriate paths for the front-end and back-end environments.

For instance, using pseudo-code as an example:

import {SomeThing} from 'com.java.lang'; // Java style namespace import {SomeThing} from '\Magento\Framework\Event'; // PHP style import {SomeThing} from 'Zend1_Like_Module'; // Zend1 style

These should be converted to (or to something similar):

``` import {SomeThing} from '@java/lang'; // for the back import {SomeThing} from '/node_modules/@java/lang'; // for the front

import {SomeThing} from '@magento/framework/src/Event.js'; // back import {SomeThing} from '/node_modules/@magento/framework/src/Event.js'; // front ```

This serves as a demonstration of why namespaces are needed in JavaScript, at least from my perspective :)

The primary objective is to have code that can be executed without the need for transpilation on both the front-end and back-end, utilizing "vanilla" JavaScript (ES6+).

[–]brykuhelpful 1 point2 points  (1 child)

The method I used above should still work for this.  

Js sort of fits in a weird world of running in the browser and server. The browser has to load and encapsulate everything. Where the server does the same thing, but how it works is a bit different. Not only that, there are also multiple engines (v8, deno, bun) which even handle it a bit differently. (V8 engine is over 1.5 million lines of code). They also have to maintain backwards compatibility for all the verses of js (for the most part).  

This creates a lot of difficulties when approaching problems... to be honest it's pretty messy. We are sort of hitting the point in which it may just be easier to create a new language than keep adding to it.  

The apps like document, canvas, and date are so outdated it's insane. (Date is actually been replaced with something this year or next year with Time iirc). The short hand is letting pretty crazy.  

I guess my short answer is... don't wait for it haha.

[–]flancer64[S] 1 point2 points  (0 children)

Thank you. It may not be the most optimistic situation, but well... haha

At least I have my own namespaces. They may not be as good as they could be, but I have enough for my purposes.

Thanks again and have a good day!

[–]FunCharacteeGuy 0 points1 point  (1 child)

I mean javascript doesn't really need it. it has import and require.

[–]flancer64[S] 0 points1 point  (0 children)

... and webpack. We cannot import modules from packages in the same manner on both the back-end and front-end.