If you are curious programmer who likes reading source code and if you happen to have ever read Node.js's source you would have noticed this term called primordial
and would have seen it's usage like:
const {
ObjectCreate,
ObjectDefineProperty,
RegExp,
RegExpPrototypeExec,
SafeArrayIterator,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
} = primordials;
You get into your REPL and hit primordials
and notice that Uncaught ReferenceError: primordials is not defined
So, what are these primodials?
From the comments in source:
This file subclasses and stores the JS builtins that come from the VM
so that Node.js's builtin modules do not need to later look these up from
the global proxy, which can be mutated by users.
Interesting, so this is here to avoid any problems that might occur in case user overwrites anything in prototype
chain or the global proxy.
Example:
Say somewhere in the node internal logger lib we have:
Object.defineproperty(logger, 'enabled', {
get() {
return test();
},
configurable: true,
enumerable: true
});
If a user defined lib overrides or deletes Object.defineproperty
the internal logger's defineproperty
will fail to execute or behave as per expectations, hence we would need primordials thereby we could rewrite the same code as:
const {
ObjectDefineProperty,
} = primordials;
ObjectDefineProperty(logger, 'enabled', {
get() {
return test();
},
configurable: true,
enumerable: true
});
But there is also a performance concern:
Use of primordials have sometimes a dramatic impact on performance, please
benchmark all changes made in performance-sensitive areas of the codebase.
See: https://github.com/nodejs/node/pull/38248
Digging into the code:
First up we notice aliases for Reflect functions.
const {
defineProperty: ReflectDefineProperty,
getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
ownKeys: ReflectOwnKeys,
} = Reflect;
Then we have two interesting methods uncurryThis
and applyBind
which are basically:
func => Function.prototype.call.bind(func)
func => Function.prototype.apply.bind(func)
It is using bind.bind(apply)
or bind.bind(call) to avoid using
Function.prototype.bindand
Function.prototype.applyor
Function.prototype.call` after users may have mutated it.
But delete Function.prototype.call
will still break most of node, also also delete Array.prototype[Symbol.iterator]
will break everything most anywhere there's for-of
, or spreading
an array. [Thanks to @ljharb for this info]
Methods that accept a variable number of arguments, and thus it's useful to
also create ${prefix}${key}Apply
, which uses Function.prototype.apply
,
instead of Function.prototype.call
, and thus doesn't require iterator destructuring.
There are some interesting cases:
It creates copies of configurable value properties, URI handling functions, namespace objects and intrinsic objects.
Intrinsic objects that require a valid this
to call static methods are treated as a special case.
Abstract intrinsic objects that are not directly exposed on the global object is another case.
Creates safe iterators, Map, WeakMap, Set, WeakSet, FinalizationRegistry and Promises.
createSafeIterator?
const createSafeIterator = (factory, next) => {
class SafeIterator {
constructor(iterable) {
this._iterator = factory(iterable);
}
next() {
return next(this._iterator);
}
[SymbolIterator]() {
return this;
}
}
ObjectSetPrototypeOf(SafeIterator.prototype, null);
ObjectFreeze(SafeIterator.prototype);
ObjectFreeze(SafeIterator);
return SafeIterator;
};
Also, it is worth noting about frozen-intrinsics
flag which enables experimental frozen intrinsics like Array
and Object
. Support is currently only provided for the root context and no guarantees are currently provided that global.Array
is indeed the default intrinsic reference. If you were to start your REPL with --forzen-intrinsics
flag:
> Object.defineProperty
> delete Object.defineProperty
> Object.defineProperty = null
> Object.defineProperty
Hope you had fun scratching the surface on Primordials!
P.S: Thanks to Guy Bedford for reviewing this post.
Feel free to share this article. You may as well ping me on Twitter.
Published