Going Deep with XS: deepEqual and structuredClone
Comparing and copying JavaScript objects are fundamental operations that are remarkably complex to implement correctly and efficiently. Because these operations are not built into the JavaScript language, various solutions have arisen to make life easier for developers. Web browsers provide the structuredClone
function to make a deep copy of an object. Node.js provides deepEqual assertions to perform deep comparison of objects and the is-equal
module provides a faster implementation as an npm module. Embedded developers who need these operations have had to create their own ad-hoc solutions. Until now.
The Moddable SDK now includes structuredClone
and deepEqual
functions that emulate the behaviors of these functions in web browsers and Node.js. Read on to learn how Moddable made these powerful functions fast on embedded devices while staying compatible with the implementations on the web platform.
Fast. And Small.
To deliver the optimal performance on relatively slow embedded devices, the Moddable SDK implementations of deepEqual
and structuredClone
are written in C and compiled to native machine code. In addition, they are implemented using the internal data structures of the XS JavaScript engine. This combination allows them to be as fast as possible.
As an added benefit, these implementations are also quite small. Despite providing very complex behaviors, the implementations of both deepEqual
and structuredClone
are just a few KB of code each (the exact size varies by target CPU, compiler, etc). This small code footprint makes them practical to include in projects that require the functionality.
Compatible with the Web
Compatibility with APIs from the web platform is an important goal for the Moddable SDK implementations of deep copy and deep clone. Because Moddable did the extra work to deliver a well-known API, developers can apply their knowledge from the web to embedded systems, and potentially even share code between the two.
structuredClone
is a true standard. It is defined by WHATWG as part of the HTML Living Standard. The Moddable SDK implements this specification precisely, varying only by omitting support for Web-only objects like DOMException
and Blob
that don't exist on embedded devices. structuredClone
in the Moddable SDK handles circular references correctly and is able to clone preloaded objects stored in read-only flash memory. It also supports the transferables
option which helps reduce memory use when cloning TypedArray
instances in some situations.
deepEqual
isn't quite standard. There are many valid ways to compare objects in JavaScript, and a consensus has not emerged. The Moddable SDK adopts the Node.js behavior for deep object comparison because it is a very reasonable approach that is widely used. As with structuredClone
, deepEqual
supports circular references and preloaded objects in read-only flash memory. Because there is no formal standard, the Moddable SDK implementation is based on review of the Node.js documentation and source code.
To ensure compatibility with the web, the Moddable SDK implementations of structuredClone
and deepEqual
are tested using the same test cases as the web versions. To allow the tests to be run on embedded devices, Moddable split the structuredClone tests and deepEqual tests across multiple files, but otherwise the tests are unchanged. Reading and running the tests is a good way to explore the capabilities of these functions. The tests can be run in the simulator and on some embedded devices (including ESP32) using testmc.
Easy to Use
Using deepEqual
and structuredClone
in your projects is easy. There are just three steps.
First, add the modules to your project's manifest. These functions aren't built-in so that they don't increase the code size of project's that don't use them.
"include": [
"$(MODULES)/base/deepEqual/manifest.json",
"$(MODULES)/base/structuredClone/manifest.json"
]
Next, import the deepEqual
and structuredClone
functions in your source code.
import deepEqual from "deepEqual";
import structuredClone from "structuredClone";
You're now ready to use the functions.
const a = {a: 1, b: Uint8Array.of(1, 2, 3), c: "0"};
const aCopy = structuredClone(a);
deepEqual(a, aCopy); // true
aCopy.c = 0;
deepEqual(a, aCopy); // true
deepEqual(a, aCopy, {strict: true}); // false
To learn more about these functions, check out the Moddable SDK documentation for deepEqual and structuredClone.
Conclusion
The deep equality and deep clone operations are now an officially supported part of the Moddable SDK. These operations are often useful in frameworks that pass objects as messages. For example, they are already being used in Node-RED MCU Edition to clone and compare messages.
The fast implementations and small code footprint make these new functions practical to use in embedded projects. By emulating behaviors from the web platform for these deep object operations, Moddable makes it easier for web developers to use more of their deep knowledge to build embedded software.
Note: Moddable thanks Jordan Harband for his helpful advice when we were trying to decide what behavior to implement for deep equality. Jordan's is-equal
module was a great reference in understanding the behavior of Node.js for deep equality, and we use its tests to validate our implementation. Thank you, Jordan!