Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Moddable XS #12

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

cmidgley
Copy link

The Moddable (github) open-source project is a full JavaScript (2022) implementation for microcontrollers (such as the ESP32 and ESP8266) that supports TypeScript. Since it supports tiny microcontrollers, they do some tricks to keep RAM usage small by freezing objects into flash, including the ability to execute the JavaScript global-executed code during compilation on the host machine (Windows, Mac, Linux) and freezing the results of created objects into flash (called preload). The result is that the DI maps used to track registrations that get executed during preload end up freezing the maps and runtime registrations fail.

Additionally, Moddable does not support NPM but does support git. However, DI currently cannot be installed using git directly, as the build process that creates the required dist output is not getting executed. Also, Modable requires a manifest.json file at the root to specify the rules for the build, conceptually similar to a package.json file.

The following changes are proposed in this pull request:

  • Translate frozen maps to be writable: Maps have been moved into a object called writeableDiContainerMaps to centralize all maps into a single managed object. The maps are retrieved when needed using diContainerMaps which returns the maps as-is as long as they are not frozen, but if they are it copies the frozen (flash memory) maps into non-frozen (RAM) maps. This translate occurs only once, on the first use of a map at runtime.
  • Adjust build process to support git-based installs: A minor change to package.json was made to allow builds via prepare (shifting away from just preversion).
  • Added a Moddable manifest.json file: To support Moddable builds, a simple manifest.json file has been added to the root.

This does not meaningfully affect performance, as the only impact is one redirect via the diContainerMaps to get the maps and one runtime check using Object.isFrozen. It has been tested on Node and Moddable (on ESP32 and their Windows-based simulator).

- During moddable preload, container registration occurs and is placed into the maps in flash that are frozen
- Container maps are refactored to use an interface for containing all maps, and clones all container maps, if and only if the are frozen, so they can become writable in RAM
- Change only affects preloaded frozen maps, no meaningful change to Node environments aside from one level of indirection on accessing maps
- Added moddable.json file, which is similar to package.json for Moddable XS
- remove build from preversion
- add prepare script to do build
- This allows a git-based project dependency to build and get dist artifacts
…uilding on npm i. removed /dist/ from gitignore to use that instead.
@wessberg
Copy link
Owner

wessberg commented Oct 18, 2024

Hi @cmidgley.

Thanks for your contribution. I've been updating the codebase with more modern language features and TypeScript 5.6 compatibility, so there are significant conflicts between the two branches.

As I've also mentioned in the past, I would like to help you bring first-class support for Moddable into DI.

But, after looking at the code, and the scope of the changes, it does seem like the best approach here is to expose more hooks in the base DIContainer class that can be subclassed, so that in theory you can write a ModdableDIContainer or something akin to that, which is then extended with your logic changes. And then instead of exposing it via a dist folder here, maybe the best approach is that you release that in your own Github repo, which I'll gladly link to in the README. What do you think?

@cmidgley
Copy link
Author

I saw some di-compiler changes a bit ago and have a work item to trial merge them. It's wonderful to see this excellent project getting a fresh coat of paint. I'm especially hopeful this will fix a tree-shaking problem I've been having with TSC 5.5.3! I didn't want to submit an issue as I knew it wasn't a supported version, so I've been living with the side effects.

Using hooks is a good strategy if you are willing to invest the time. For background, Moddable XS uses something similar to Secure ECMAScript on the global namespace, which means anything that runs before the first microtask gets frozen (read-only).

I've not spent a lot of time thinking about this, but I see two broad-stroke approaches:

  1. Present a whole container create/get interface similar to the PR, where the container is abstracted into an interface and presented as a whole object (containing constructorArguments, serviceRegistry, and instances). The hook(s) would present interfaces to create the container, and get the container, as a whole. The assumption would be that the individual properties within it are Map objects as they are today.

  2. Present a discrete CRUD-like interface for container element access, where the interface would present operations on the container (such add-to-registry, get-instance, etc.), replacing the current Map based properties. This is more effort to implement with a greater risk of breaking, but it would allow the consumer to use any backing store. A Moddable custom container could use this to reduce the memory footprint and avoid duplicating the frozen registrations as it could internally use two backing containers (one frozen, one not).

Personally, I'd keep it simple / reduce risk, and keep the Map objects, but I'll work with you on whatever you propose.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants