Introduction

Because I'm a Pythonista...

Oh, how I wanted to relate the previous CommonJS series to Python.

But it was impossible. Now I know why. CommonJS is not a JavaScript language specification, but a "specification for an execution environment to efficiently use JavaScript" (there's nothing like that in Python). On the other hand, ES Modules is a language specification set by ECMAScript (the standard specification for JavaScript), so it was easier to explain "as if it were Python".

 

Explaining ES Modules while talking about Python

  • In the ES Modules module system, the export keyword is used to expose variables you want to export to the outside.
    • How would that be in Python? It's just like defining a normal variable. In Python, "everything is public by default," "you need to do a bit of work to make it private," but it's the reverse in ES Modules.
  • However, there is a special variable called default. This is a reserved word and a variable exclusively for exports. Don't worry about it. You can do everything you want without default.
  • When importing, you do either import defaultItem, { VAR1, VAR2 } from MODULE or import * as module from MODULE.
    • How would that be in Python? It's like saying use from MODULE import VAR1, VAR2 or import MODULE.

In the execution environment of ES Modules, you "export using the export keyword and import using something equivalent to Python's from ... import ... or import ...". That's all there is to it.

 

How Mr. Green handled the confusing issue between ES Modules and CommonJS

"Since ES Modules is a language specification, the one using keywords is ES Modules". That's all. Keywords are also known as reserved words. In Python, you can see a list with import keyword; print(keyword.kwlist).

// What CommonJS calls export is actually nothing more than
// an assignment or value addition to the module.exports object.
// Import also leverages the power of functions.
exports.increment = (i) => i + 1;
module.exports.sunday = "Sunday";
console.info(require("./foo.js"))

// Since ES Modules is a language specification, it uses keywords liberally.
const a = "A"
export { a }
import { a } from "./foo.js"

 

Here are the experiment notes

node -v
# v20.3.0

# Let's first create the exporting side.
echo '
export default "DEFAULT"
const a = "A"
export { a }
export const b = "B"
' > export-side.mjs

# Let's try Python's equivalent of from ... import ...
echo '
import defaultItem, { a, b } from "./export-side.mjs"
console.info({ defaultItem, a, b })
' > import-side.mjs
node import-side.mjs
# { defaultItem: 'DEFAULT', a: 'A', b: 'B' }

# So, either the first (default) or the second (a, b) can be omitted.
echo '
import defaultItem from "./export-side.mjs"
console.info({ defaultItem })
' > import-side.mjs
node import-side.mjs
# { defaultItem: 'DEFAULT' }

echo '
import { a, b } from "./export-side.mjs"
console.info({ a, b })
' > import-side.mjs
node import-side.mjs
# { a: 'A', b: 'B' }

# Let's try Python's equivalent of import ...
echo '
import * as imported from "./export-side.mjs"
console.info({ imported })
' > import-side.mjs
node import-side.mjs
# {
#   imported: [Module: null prototype] { a: 'A', b: 'B', default: 'DEFAULT' }
# }

# The main question is, can we have a const default? No, it's not allowed.
# In Python, words like if cannot be used as variable names either.
echo '
const default = "DEFAULT"
' > export-side.mjs
node export-side.mjs
# const default = "DEFAULT"
#       ^^^^^^^
# SyntaxError: Unexpected token 'default'

# Another question is, what's this `imported: [Module: null prototype]`?
# I thought it was something like a parent class in Python, but apparently not.
# This seems to be related to JavaScript's prototype-based inheritance.
# For now, let's just figure out how to create it.
echo '
const normal = {}
normal.default = "DEFAULT"
normal.a = "A"
normal.b = "B"

const obj = Object.create(null)
obj.default = "DEFAULT"
obj.a = "A"
obj.b = "B"

console.info({ normal, obj })
' > prototype-test.mjs
node prototype-test.mjs
# {
#   normal: { default: 'DEFAULT', a: 'A', b: 'B' },
#   obj: [Object: null prototype] { default: 'DEFAULT', a: 'A', b: 'B' }
# }
# We did it!