DEV Community

Eckehard
Eckehard

Posted on

How to avoid namespace pollution in Javascript

I found this brilliant post about namespace pollution, which notes that - beside of conflicting name definitions - using global variables may have impact on memory consumption (see also this post):

"As variables lose scope, they will be eligible for garbage collection. If they are scoped globally, then they will not be eligible for collection until the global namespace loses scope..."

In languages like C++ or Delphi, naming conflicts between libraries can easily be solved: If there are identical names in different libraries, the name can be qualified by adding the library name:

  • libA defines myVariable
  • libB defines myVariable

Your app can use libB.myVariable or libA.myVariable or myVariable, if no conflict occured - Simple solution

In Javascript, name clashes cannot be solved this way. For Variables, we can use var instead of let, but this may cause hard to track errors. For functions, I currently see no such solution.

Using named imports of modules is not a similar elegant solution. I was wandering, if there are better solutions or recommendations for Javascript?

Discussion (29)

Collapse
artydev profile image
artydev • Edited on

Hy Eckehard,

Look at this book online from Addy Osmani

Especialy the Revealing Pattern : RP

Regards

var myRevealingModule = (function () {

        var privateVar = "Ben Cherry",
            publicVar = "Hey there!";

        function privateFunction() {
            console.log( "Name:" + privateVar );
        }

        function publicSetName( strName ) {
            privateVar = strName;
        }

        function publicGetName() {
            privateFunction();
        }


        // Reveal public pointers to
        // private functions and properties

        return {
            setName: publicSetName,
            greeting: publicVar,
            getName: publicGetName
        };

    })();

myRevealingModule.setName( "Paul Kinlan" );
Enter fullscreen mode Exit fullscreen mode
Collapse
efpage profile image
Eckehard Author

Thank you for the hints.

I think we can achieve the same result using classes, which has the advantage that you can use setters and getters.

I just wanted to avoid the module name on every function call. Maybe in Javascript, the only option are named imports.

Collapse
peerreynders profile image
peerreynders

I think we can achieve the same result using classes

The entire idea behind the revealing module pattern is to keep the "private" parts truly "private" inside a function closure created via an intermediately invoked function expression (IIFE). JavaScript always had function closures.

OOP with Functions in JavaScript

Private class fields are only part of the Class field declarations for JavaScript and private methods are part of the Private methods and getter/setters for JavaScript classes TC39 proposals which are currently at Stage 3 - which means they may become part of ES2023 if they go to Stage 4 before then.

Babel right now includes @babel/plugin-proposal-class-prope... and @babel/plugin-proposal-private-met... by default in @babel/preset-env

Can I Use: JavaScript classes: Private class fields

Can I Use: JavaScript classes: Private class methods

MDN: Private Class Features

which has the advantage that you can use setters and getters.

In the example given the object returned includes the getName() accessor and the setName() mutator. But perhaps you were referring to the getter and setter object property syntax:

const myModule = (() => {
  let name = 'Ben Cherry';

  return {
    get name() {
      return name;
    },
    set name(value) {
      name = value;
    },
  };
})();

console.log(myModule.name); // "Ben Cherry"
myModule.name = 'Paul Kinlan';
console.log(myModule.name); // "Paul Kinlan"
Enter fullscreen mode Exit fullscreen mode

It just works!

Collapse
artydev profile image
artydev • Edited on

Hy Eckehard

In a module you cant import whatever variables from another one, and use them without any prefix.

TodoListTutor

Feel free to correct :-)

Regards

PS : Have you considered turning DML UI Elment in customeElements ?

TodoListCE

Thread Thread
efpage profile image
Eckehard Author

No, we had a focus on long term compatibility (expect IE), CE had a limited support some years ago.

I try to keep modules as small as possible. But maybe somebody could write a wrapper as a separate module? On the other hand, what's the advantage?

Thread Thread
artydev profile image
artydev • Edited on

For the CE , it was in fact to illustrate,how easy it was to create one.
You can create a lib containing only DML-CE like Shoelace for CSS

But if IE is a concerned, you can look at a article I post WE
which allows create CE without any polyfill that works even in IE.

Regards

Thread Thread
peerreynders profile image
peerreynders

People get easily overwhelmed with his contributions so it's best to always pass along:

Thread Thread
artydev profile image
artydev • Edited on

I know, unfortunately.
Thank you peerenders :-)

Collapse
artydev profile image
artydev

Inside a module you can import only what you want from antother one, like this

import { make, selectBase, unselectBase } from "./dml";
Enter fullscreen mode Exit fullscreen mode
Thread Thread
efpage profile image
Eckehard Author

Yes, I know. We should use ES-modules instead of scripts. But - they come at a price and I did not find a satisfying solution for me right now. Please let me explain:

First, we should have a look, how modules are organized in other languages like C++ or Delphi. Generally, you need to import any module explicitly into your source. Each module has a header, that defines precisely, what is exported, very similar to what you do in Javascript. Modules often are based on other Modules, so they may export a very large number of symbols that are accessible in your source then.

Generally you try to keep your interfaces narrow, but complex systems may export 10.000 symbols that - in principle - are acessible in your source.

BUT: Behind the scene, we have an intelligent linker that checks, which parts or the source was used in your code and just extracts, what is needed. In large hierarchies you may have hundreds of name clashes, which is absolutely no problem as these languages have a different way to deal with a clash.

In Javascript, such a situation would crash the whole system. So, what are the drawbacks:

a) Import/export is only supported on the top level, you cannot define a more fine grained access control. Compiled languages usually know different levels of visibility (privat, public, published, ...) outside and inside of classes. For me, the benefit though is a bit limited.

b) Putting every used symbol in an import list is kind of boring. Think of a large module with hundreds of exports. This is a job a computer could do far better than me, and I think, we shouldn't do the work for the computers, it should be the other way around.

c) Maybe you are a bit lazy and use the whole export list as import. This will pollute your namespace with lot´s of unused symbols, which is just the topic of this post.

So, I´m just wondering if there are other solutions that have less drawbacks. Maybe we could have some kind of "intelligent loader", that just loads the parts of a module when it´s needed first? Or at least a automatic import list generator? Or there is a different way to organize the code?

In the example above, DML exports lot´s of handy definitions like this:

const PI2 = 2 * Math.PI
const _style = "style"
const _bold = "font-weight: bold;"
const _italic = "font-style: italic;"
const _fs = "font-size: "
const _bigtext = "font-size: 130%;"
const _bg = "background-color: "
const _bgred = "background-color: red;"
const _bgred2 = "background-color: #f50;"
const _bgy = "background-color: #ffc;"
const _bggreen = "background-color: #695;"
const _bgblue = "background-color: blue;"
const _bgorange = "background-color: #fc0;"
const _bgsilver = "background-color: silver;"
const _bgyellow = "background-color: #ffffee;"
const _bgwhite = "background-color: white;"
const _bgblack = "background-color: black;"
const _bgtrans = "background-color: rgba(0,0,0,0.05);"
const _bgwtrans = "background-color: rgba(255,255,255,0.5);"
const _red = "color: red;"
const _blue = "color: blue;"
const _navy = "color: navy;"
const _white = "color: white;"
const _yellow = "color: yellow;"
...
Enter fullscreen mode Exit fullscreen mode

This is just for convenience, but adding an "export" to every definition will blow up the code and does not really solve the namespace issues.

I would really appreciate to find a better solution for this topic. (Please don´t tell me I´m a bad programmer... As I mentioned already: There are different ways to catch a tiger.)

Thread Thread
artydev profile image
artydev • Edited on

I will never say to anyone he is a bad programmer,
again, I am very happy to have discovered your library :-)

Your problem could be submited to Andrea Giammarchi from WebReflections.
I am pretty sure he could respond about the possibility of a 'lazy loading'

Regards

Thread Thread
peerreynders profile image
peerreynders

1) It may help to organize things into fewer values

const _bg = Object.freeze({
  red: 'background-color: red;',
  red2: 'background-color: #f50;',
  y: 'background-color: #ffc;',
  green: 'background-color: #695;',
  blue: 'background-color: blue;',
  orange: 'background-color: #fc0;',
  silver: 'background-color: silver;',
  yellow: 'background-color: #ffffee;',
  white: 'background-color: white;',
  black: 'background-color: black;',
  trans: 'background-color: rgba(0,0,0,0.05);',
  wtrans: 'background-color: rgba(255,255,255,0.5);',
});

const _clr = Object.freeze({
  red: 'color: red;',
  blue: 'color: blue;',
  navy: 'color: navy;',
  white: 'color: white;',
  yellow: 'color: yellow;',
});

export {
  _bg,
  _clr
};
Enter fullscreen mode Exit fullscreen mode

2) These type of values are typically managed by an entirely different tool, e.g. Sass (also gorko).

Thread Thread
efpage profile image
Eckehard Author • Edited on

There is a solution do avoid named input (the lazy way), which is not the final word, but maybe an interesting starting point:
In DML.js, we set the export:

export  {h1, h2, ....}
Enter fullscreen mode Exit fullscreen mode

Now we import our library as dml and make it accessible in the global scope

  <script type="module">  
    import * as dml from "../lib/DML.js"

    dml.h1("h1 test");

    // Make all functions in dml global
    (function () {
      for (const [key, value] of Object.entries(dml)) {
        window[key] = value  // Store the references as variables
      }

    })()
    h1("h1 test")
  </script>
Enter fullscreen mode Exit fullscreen mode

I know this is a bad solution for many reasons. But it might be a starting point:

The solution above defines all symbols as global variables, which we wanted to avoid. I just did not find a way to define scoped (e.g. local) variables that way. If we could manage to create scoped variables, this has many advantages:

  • We could use all elements of any library without qualifiers in a local context
  • All definitions are scoped, so leaving the scope will delete the definitions
  • No name clashes, as all definitions are local.
  • We can use dynamic import, which is pretty close to lazy loading in some cases.

So, my question is: Does anybody know a way to create local variables or const dynamically in Javascript?

The final solution could be something like this:

  <script type="module">  
    import * as dml from "../lib/DML.js"

    // Make all functions in dml local
    (function () {
      for (const [key, value] of Object.entries(dml)) {
        ????????????[key] = value  // Your Idea here
      }
      h1("h1 test")  // call h1 INSIDE the function 
    })() // leave the scope
  </script>
Enter fullscreen mode Exit fullscreen mode
Thread Thread
artydev profile image
artydev • Edited on

Perhaps using 'eval' ?
But as you know it's not recommended.

Thread Thread
peerreynders profile image
peerreynders • Edited on

1.) It is not uncommon for style guides to discourage the use of wildcard imports even when the language supports it. Example:

Wildcard imports should not be used

On the principle that clearer code is better code, you should explicitly import the things you want to use in a module. Using import * imports everything in the module, and runs the risk of confusing maintainers. Similarly, export * from "module"; imports and then re-exports everything in the module, and runs the risk of confusing not just maintainers but also users of the module.

Also keep in mind that in C/C++ include occurs at compile-time so there is no runtime cost to dumping a boatload of unused names into the code. In JavaScript import happens at run time so potential for "more work" comes with the potential for undesirable run time consequences.

Other than that, for example, Google's style guide with regards to imports.


2.) In your example it looks like you are trying to export functions that are only capable of creating a single type of HTML element

Prior art:

import { h } from 'preact';

h('div', { id: 'foo' }, 'Hello!');
// <div id="foo">Hello!</div>

h('div', { id: 'foo' }, 'Hello', null, ['Preact!']);
// <div id="foo">Hello Preact!</div>

h(
    'div',
    { id: 'foo' },
    h('span', null, 'Hello!')
);
// <div id="foo"><span>Hello!</span></div>
Enter fullscreen mode Exit fullscreen mode

For a while React also had react-dom-factories:

import { em, h1, span } from 'react-dom-factories';

h1({ id: 'my-heading' }, span(null, em(null, 'Hell'), 'o'), ' world!');
Enter fullscreen mode Exit fullscreen mode

However support was dropped as JSX is the accepted standard among React developers.
Other helper examples: hyperscript-helpers, react-hyperscript.


3.)

So, my question is: Does anybody know a way to create local variables or const dynamically in Javascript?

Basically strict mode which is the default in ES2015 modules makes that impossible.

However for the time being one can still pull a stunt like this:

// file: with-fn.js
// source: https://twitter.com/WebReflection/status/1411677706287255552
// caveat emptor: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with#ambiguity_contra
//
const withFnCache = new WeakMap();

function cacheWithFn(fn) {
  const wrapped = Function(
    'with(arguments[0])return(' +
      fn +
      ').apply(this,[].slice.call(arguments,1))'
  );
  withFnCache.set(fn, wrapped);
  return wrapped;
}

function withFn(fn) {
  const w = withFnCache.get(fn);
  return w ? w : cacheWithFn(fn);
}

export { withFn };
Enter fullscreen mode Exit fullscreen mode
// file: shared.js
const append = DocumentFragment.prototype.append;

function toFragment(children) {
  const fragment = document.createDocumentFragment();
  append.apply(fragment, Array.isArray(children) ? children : [children]);
  return fragment;
}

function createElement(tagName, children) {
  const element = document.createElement(tagName);
  element.appendChild(toFragment(children));
  return element;
}

export { createElement };
Enter fullscreen mode Exit fullscreen mode
// file: libA.js
import { createElement } from './shared.js';

function h1(...children) {
  return createElement('h1', children);
}

export { h1 };
Enter fullscreen mode Exit fullscreen mode
// file: libB.js
import { createElement } from './shared.js';

function em(...children) {
  return createElement('em', children);
}

export { em };
Enter fullscreen mode Exit fullscreen mode
<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Faking implicit imports</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <script type="module">
     import { withFn } from './with-fn.js';
     import * as libA from './libA.js';
     import * as libB from './libB.js';

     const imports = Object.assign({}, libA, libB);
     const app = withFn(appFn);

     document
       .querySelector('body')
       .appendChild(app(imports, 'Test ', 'this!'));

     function appFn(first, last) {
       // `h1` and `em` aren't declared anywhere
       return h1(first, em(last));
     }
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

// file: with-imports.js
function withImports(imports, fn) {
  const h = Function(
    'imports',
    'args',
    `with(imports)return(${fn}).apply(this, args)`
  );
  return invokeWithImports;

  function invokeWithImports(...args) {
    return h(imports, args);
  }
}

export { withImports };
Enter fullscreen mode Exit fullscreen mode
<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Faking implicit imports</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <script type="module">
     import { withImports } from './with-imports.js';
     import * as libA from './libA.js';
     import * as libB from './libB.js';

     const imports = Object.assign({}, libA, libB);
     const app = withImports(imports, appFn);

     document.querySelector('body').appendChild(app('Test ', 'this!'));

     function appFn(first, last) {
       // `h1` and `em` aren't declared anywhere
       return h1(first, em(last));
     }
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode
Thread Thread
efpage profile image
Eckehard Author

I like any kind of dirty tricks, but 'eval' is really nasty. As this may cause serious security issues I would not try to use it. But I´m sure there will be a solution without.

Thread Thread
efpage profile image
Eckehard Author

Andrea Giammarchi reminded me, that rollup.js might be perfect. That´s indeed a smart solution!

Thread Thread
artydev profile image
artydev • Edited on

Great, Andrea is an awesome guy
In fact I have published your TodoList using rollup and it's tree shaking feature :-)

Thread Thread
peerreynders profile image
peerreynders • Edited on

MDN: Function:

Calling the constructor directly can create functions dynamically but suffers from security and similar (but far less significant) performance issues to Global_Objects/eval. However, unlike eval, the Function constructor creates functions that execute in the global scope only.

Also functions created with Function don't default to strict mode so it is possible to use the with statement which has been not welcome for the last decade.

The suggested alternative leads us back to

<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Giving up on faking implicit imports</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <script type="module">
     import * as libA from './libA.js';
     import * as libB from './libB.js';
     const h = Object.assign({}, libA, libB);

     document.querySelector('body').appendChild(app('Test ', 'this!'));

     function app(first, last) {
       return h.h1(first, h.em(last));
     }
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

that rollup.js might be perfect.

It would let you work with smaller modules which are less prone to having a large number of exports that are then stitched together at build time - so wildcard imports would be much less problematic. In my judgement rollup.js is probably the most sane bundler solution at this time (even when esbuild is faster; Parcel is way too magical for my taste).

Collapse
peerreynders profile image
peerreynders • Edited on

Your app can use libB.myVariable or libA.myVariable or myVariable, if no conflict occured - Simple solution

In Javascript, name clashes cannot be solved this way.

Using named imports of modules is not a similar elegant solution.

// file: libA.js
const myVariable = 42;

export {
  myVariable
};
Enter fullscreen mode Exit fullscreen mode
// file: libB.js
const myVariable = 9007199254740881;

export {
  myVariable
};
Enter fullscreen mode Exit fullscreen mode
<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Avoiding Namespace Pollution</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <p>Check the console</p>
    <script type="module">
      import * as libA from './libA.js';
      import * as libB from './libB.js';

      console.assert(libA.myVariable === 42);
      console.assert(libB.myVariable === 9007199254740881);
      console.log('done');
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Are the extra * as characters really worth fretting about?

Collapse
efpage profile image
Eckehard Author

No, but this forces to use the libary name on any function or variable of any library you use. The C++-solution is far more handy, only to use a qualifier where necessary.

Collapse
peerreynders profile image
peerreynders
// file: libA.js
const myVariable = 42;
const someVariable = 10;

export {
  myVariable,
  someVariable,
};
Enter fullscreen mode Exit fullscreen mode
// file: libB.js
const myVariable = 9007199254740881;
const anotherVariable = 1000;

export {
  myVariable,
  anotherVariable
}
Enter fullscreen mode Exit fullscreen mode
<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Avoiding Namespace Pollution</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <p>Check the console</p>
    <script type="module">
     import * as libA from './libA.js';
     import * as libB from './libB.js';
     const { someVariable} = libA;
     const { anotherVariable} = libB;

     console.assert(libA.myVariable === 42);
     console.assert(libB.myVariable === 9007199254740881);
     console.assert(someVariable === 10);
     console.assert(anotherVariable === 1000);

     console.log('done');
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The C++-solution is far more handy, only to use a qualifier where necessary.

Douglas Crockford, JavaScript - The Good Parts, 2008; p.2:

JavaScript is most despised because it isn’t some other language. If you are good in some other language and you have to program in an environment that only supports JavaScript, then you are forced to use JavaScript, and that is annoying. Most people in that situation don’t even bother to learn JavaScript first, and then they are surprised when JavaScript turns out to have significant differences from the some other language they would rather be using, and that those differences matter.

Thread Thread
efpage profile image
Eckehard Author

2008 is long ago. Javascript has made a good progress over the last years and gained a lot of strength (and speed). I still think the designers made some strange decisions, but overall I´m not unhappy. The lack of strong typing makes less trouble than I expected and gives you some flexibility, that other languages miss.

I think it is absolutely OK to compare Javascript with other languages. Lot´s of things HAVE been changed already, but for some things we need to find tricks and solutions to make better use of it. That´s the reason we are talking about.

Thread Thread
peerreynders profile image
peerreynders

2008 is long ago.

The quote is timeless because people constantly try to find their language inside of JavaScript and only end up perpetually creating grief for themselves. The message of the book was to let go of any preconceived notion from other programming languages (and paradigms) and to simply deal with JavaScript on its own terms. That advice is as valid today as it was back then.

It's natural for people to stick to the familiar but when it comes to programming languages outside of the OOP, imperative bubble (C, C++, C#, Java, Python etc.) it's time get a fresh start.

JavaScript uses a familiar C-style syntax but Brendan Eich had Scheme on the brain when he designed it - and it shows to this day (Yes, JavaScript is a Lisp).

With TypeScript Microsoft (being Microsoft) tried to gloss over that fact:

TypeScript began its life as an attempt to bring traditional object-oriented types to JavaScript so that the programmers at Microsoft could bring traditional object-oriented programs to the web.

… but as they were actually trying to type (most) JavaScript code they had to go far beyond class and interface.

In fact the introduction of class in ES2015 was controversial as it was seen as a means to make JavaScript more palatable to programmers coming from mainstream OOP languages - especially give that even in 1994 GoF recommended (p.20):

Favor object composition over class inheritance.

Also JavaScript was already truly object-oriented given that code could always construct objects even without facilities such as classes or even constructor functions i.e .it was never class-based.

And as the MDN states:

Classes are a template for creating objects.

In JavaScript objects can be augmented - so an object's class-membership isn't permanent for life. And in ES5 factory functions were in common use for creating objects.

These days classes have become an important part of "the platform" - given that the original Web API was already largely object-based, classes became the mechanism by which natively implemented facilities are now extended with JavaScript.

But to some degree this has more to do with "the browser as an environment that can be automated with JavaScript" rather than "JavaScript the Programming Language" - even from the very beginning people have conflated (the "messy") DOM API (a part of the browser) with JavaScript (the programming language).


Javascript has made a good progress over the last years and gained a lot of strength (and speed).

Yes, V8 is phenomenally fast but JavaScript is still an interpreted language and none of the "progress" has substantially changed the underlying character of JavaScript. JavaScript simply can't take advantage of the tricks that most compiled languages can.


I still think the designers made some strange decisions, but overall I´m not unhappy.

Whenever you run into a "strange decision" have a look at ES Discuss and TC39/proposal to see what went into making that decision - you may be surprised at the wide range of perspectives that were considered - not that it makes the decision any less weird in retrospect.


I think it is absolutely OK to compare Javascript with other languages.

On the server-side that makes sense because you can switch when you can see the balance tipping. I still regularly 🤦‍♂️ when I discover that some organization chose to use JavaScript on the server when there are clearly other, better options. The whole universal, isomorphic argument seems a bit self-serving - though in some cases for small teams it can make sense (Marko).

But when it comes to the browser there is no real alternative other than perhaps transpiling to JavaScript - so that conversation is really going nowhere.

WebAssembly:

It is also designed to run alongside JavaScript, allowing both to work together.

If you really wanted to you could use C++ Compiling a New C/C++ Module to WebAssembly (I'd rather go with Rust) - but is that always an effective solution?

And languages that require separate multi-megabyte run times aren't going to succeed outside of niche applications - JavaScript doesn't have to ship a separate runtime and Web APIs are all designed with JavaScript in mind.

On top of everything else, by necessity software running in the browser is subject to very different design constraints than software running somewhere in a cloud server data centre. Given The Mobile Performance Inequality Gap, 2021 the cavalier attitude of creating a product by indiscriminately aggregating many packages from npm may be tolerable for some server side work but is absolutely the wrong direction for client side products.

"We need this to run to on a low powered embedded system - no, wait we're not allowed to ship binary code - we can only send JavaScript and the deployed engine will already be claiming most of the system's resources."

… is probably a more appropriate perspective for approaching client-side product design.


Lot´s of things HAVE been changed already, but for some things we need to find tricks and solutions to make better use of it.

Perhaps rather than stating something like:

I'm used to doing X this way

ask something like

How exactly does one solve problem Y in the browser (with JavaScript)?

The XY Problem

Thread Thread
efpage profile image
Eckehard Author • Edited on

Sorry, but your comments are a bit off topic. At least, now I know why some people are called tech-evangelists - they cannot stop praying. You do not even know what task I want so solve, so how do you know, you have the right tools?

Please, stay on topic. There your help is much appreciated.

Collapse
efpage profile image
Eckehard Author

What about imports inside a module? This pollutes the namespace of the module, but not the global namespace:

html
<- module A <- libA
<- module B <- libB

I assume that dynamic import would also help to get short loading times in some cases.

Collapse
peerreynders profile image
peerreynders
  • I'm not sure what you are asking here - I was under the impression you were concerned about name collisions when using multiple wildcard imports in the same module.
  • "Shorter loading" times simply means that HTML and CSS loading and processing won't get blocked by JavaScript loading/parsing/executing; i.e. to have a benefit the page will have to contain static HTML and CSS.
Collapse
efpage profile image
Eckehard Author

This was a questions about the scope of the global namespace inside a module. I assume, something like window[myVariable] = "NewValue" is not possible in a module?!?

Thread Thread
peerreynders profile image
peerreynders • Edited on
  • It's only top level var and function declarations in inline JavaScript that pollute the global object (typically prevented with an IIFE). This doesn't happen in type="module" or ECMAScript modules - those names are "module global" but do not create properties on the global object.
  • const and let are block scoped so they don't affect the global object.
  • In modules you can still alter the global object via any name that is bound to it (provided that part isn't read-only). So window['myVariable'] = 'NewValue'; is possible.
  • Imported names are "module global" but do not create properties on the global object

<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Top level Variables</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <p>Check the (web) developer tools console</p>
    <script type="module">
      // global names:
      //
      // `window` - browser window only
      // `self` - browser window & web worker (including service worker)
      // `global` - Node.js only
      // `globalThis` - browser window & web worker & Node.js

      console.log('TYPE: inline module - defer by default');

      // console.log(noVariable); // Uncaught ReferenceError: noVariable is not defined
      console.assert(globalThis.hasOwnProperty('varModVariable') === false);
      console.assert(typeof varModVariable === 'undefined');
      var varModVariable = 'varModValue';
      console.assert(globalThis.hasOwnProperty('varModVariable') === false);
      console.assert(varModVariable === 'varModValue');
      console.assert(typeof globalThis.varModVariable === 'undefined');
      console.assert(typeof self.varModVariable === 'undefined');
      console.assert(typeof window.varModVariable === 'undefined');

      // console.log(constVariable); // Uncaught ReferenceError: Cannot access 'constVariable' before initialization
      console.assert(globalThis.hasOwnProperty('constVariable') === false);
      const constVariable = 'constValue';
      console.assert(globalThis.hasOwnProperty('constVariable') === false);
      console.assert(constVariable === 'constValue');

      // console.log(letVariable); // Uncaught ReferenceError: Cannot access 'letVariable' before initialization
      console.assert(globalThis.hasOwnProperty('letVariable') === false);
      let letVariable = 'letValue';
      console.assert(globalThis.hasOwnProperty('letVariable') === false);
      console.assert(letVariable === 'letValue'); // "letValue"

      console.assert(globalThis.hasOwnProperty('myModGlobal') === false);
      globalThis.myModGlobal = 'globalModValue';
      console.assert(globalThis.hasOwnProperty('myModGlobal') === true);
      console.assert(globalThis['myModGlobal'] === 'globalModValue');
      console.assert(globalThis.myModGlobal === 'globalModValue');
      console.assert(self.myModGlobal === 'globalModValue');
      console.assert(window.myModGlobal === 'globalModValue');
      console.assert(myModGlobal === 'globalModValue');
    </script>
    <script>
      (function () {
        console.log(
          'TYPE: inline text/javascript + immediately invoked function expression (IIFE)'
        );
        // console.log(noVariable); // Uncaught ReferenceError: noVariable is not defined
        console.assert(globalThis.hasOwnProperty('varIifeVariable') === false);
        console.assert(typeof varIifeVariable === 'undefined');
        var varIifeVariable = 'varIifeValue';
        console.assert(globalThis.hasOwnProperty('varIifeVariable') === false);
        console.assert(varIifeVariable === 'varIifeValue');
        console.assert(typeof globalThis.varIifeVariable === 'undefined');
        console.assert(typeof self.varIifeVariable === 'undefined');
        console.assert(typeof window.varIifeVariable === 'undefined');

        // console.log(constVariable); // Uncaught ReferenceError: Cannot access 'constVariable' before initialization
        console.assert(globalThis.hasOwnProperty('constVariable') === false);
        const constVariable = 'constValue';
        console.assert(globalThis.hasOwnProperty('constVariable') === false);
        console.assert(constVariable === 'constValue');

        // console.log(letVariable); // Uncaught ReferenceError: Cannot access 'letVariable' before initialization
        console.assert(globalThis.hasOwnProperty('letVariable') === false);
        let letVariable = 'letValue';
        console.assert(globalThis.hasOwnProperty('letVariable') === false);
        console.assert(letVariable === 'letValue'); // "letValue"

        console.assert(globalThis.hasOwnProperty('myIifeGlobal') === false);
        globalThis.myIifeGlobal = 'globalIifeValue';
        console.assert(globalThis.hasOwnProperty('myIifeGlobal') === true);
        console.assert(globalThis['myIifeGlobal'] === 'globalIifeValue');
        console.assert(globalThis.myIifeGlobal === 'globalIifeValue');
        console.assert(self.myIifeGlobal === 'globalIifeValue');
        console.assert(window.myIifeGlobal === 'globalIifeValue');
        console.assert(myIifeGlobal === 'globalIifeValue');
      })();
    </script>
    <script>
      console.log('TYPE: inline text/javascript');

      // console.log(noVariable); // Uncaught ReferenceError: noVariable is not defined
      console.assert(globalThis.hasOwnProperty('varVariable') === true);
      console.assert(typeof varVariable === 'undefined');
      var varVariable = 'varValue';
      console.assert(globalThis.hasOwnProperty('varVariable') === true);
      console.assert(varVariable === 'varValue');
      console.assert(globalThis.varVariable === 'varValue');
      console.assert(self.varVariable === 'varValue');
      console.assert(window.varVariable === 'varValue');

      // console.log(constVariable); // Uncaught ReferenceError: Cannot access 'constVariable' before initialization
      console.assert(globalThis.hasOwnProperty('constVariable') === false);
      const constVariable = 'constValue';
      console.assert(globalThis.hasOwnProperty('constVariable') === false);
      console.assert(constVariable === 'constValue');

      // console.log(letVariable); // Uncaught ReferenceError: Cannot access 'letVariable' before initialization
      console.assert(globalThis.hasOwnProperty('letVariable') === false);
      let letVariable = 'letValue';
      console.assert(globalThis.hasOwnProperty('letVariable') === false);
      console.assert(letVariable === 'letValue'); // "letValue"

      console.assert(globalThis.hasOwnProperty('myGlobal') === false);
      globalThis.myGlobal = 'globalValue';
      console.assert(globalThis.hasOwnProperty('myGlobal') === true);
      console.assert(globalThis['myGlobal'] === 'globalValue');
      console.assert(globalThis.myGlobal === 'globalValue');
      console.assert(self.myGlobal === 'globalValue');
      console.assert(window.myGlobal === 'globalValue');
      console.assert(myGlobal === 'globalValue');
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode