bartblast

bartblast

Creator of Hologram

Hey there! :slight_smile:

We need help completing Elixir’s browser runtime by porting some Erlang functions to JavaScript.

Hologram automatically transpiles Elixir, but the underlying Erlang functions - the building blocks of Elixir’s stdlib - must be ported manually.

No Erlang knowledge required - just basic JS/Elixir skills and pattern-following. Tasks: 15 min to a few hours. AI tools encouraged!

Each function you port unlocks multiple Elixir stdlib functions in the browser!

:backhand_index_pointing_right: Read the full blog post: https://hologram.page/blog/elixir-to-javascript-porting-initiative

Showing Posts 31 to 22

bartblast

bartblast OP

Creator of Hologram

Update on time-related functions

While reviewing the PRs for Erlang time functions (:os.system_time, :erlang.monotonic_time, :erlang.system_time, :erlang.time_offset, :erlang.convert_time_unit, :erlang.localtime), I realized they’re more interconnected than I initially thought.

In Erlang’s time system: Erlang System Time = Erlang Monotonic Time + Time Offset

This means these functions should delegate to each other rather than each independently calling JS APIs. I’ve documented a unified porting strategy to ensure consistency:

Erlang Time Functions Porting Strategy

Happy to discuss if you have questions or alternative suggestions!

bartblast

bartblast OP

Creator of Hologram

Closing the loop on the :os.type/0 discussion for future reference (this reasoning applies to similar cases as well):

We decided to hardcode {:unix, :web} for the client-side implementation. This aligns with our approach in other modules like :filename, where we unify path handling and treat the Hologram client platform as Unix-based.

The reasoning is twofold:

  1. :os.type/0 in Erlang/Elixir is meant to return the OS of the machine running the BEAM VM - but on the client side, our code runs in a browser sandbox, not directly on the user’s OS. Returning the actual user’s OS would be semantically incorrect and could break code that relies on :os.type/0 for platform-specific behavior (like path handling).

  2. Web APIs provide a unified, platform-independent abstraction - the browser hides OS differences from JavaScript code. Since web conventions align with Unix (forward slashes for paths/URLs), choosing {:unix, :web} is a pragmatic decision that simplifies our implementation (e.g., consistent path handling in :filename and similar modules).

For use cases where you actually need the user’s platform information (analytics, file downloaders offering platform-specific binaries, UI adaptations, etc.), we’ll expose this through a dedicated Hologram module that clearly indicates it’s about the end user’s environment rather than the execution platform.

bartblast

bartblast OP

Creator of Hologram

@tenkiller Using an external lib like ua-parser.js would be overkill IMO, especially when that package is ~12kb minzipped. We’re only detecting basic OS families here, not parsing detailed browser/device info.

:os.type/0 returns just a basic system type - in practice it’s one of these (though could you verify the complete list by checking the Erlang/OTP docs?):

Unix family:

  • {:unix, :darwin} - macOS
  • {:unix, :linux} - Linux
  • {:unix, :freebsd} - FreeBSD
  • {:unix, :openbsd} - OpenBSD
  • {:unix, :netbsd} - NetBSD
  • {:unix, :sunos} - Solaris/SunOS
  • {:unix, :aix} - IBM AIX

Windows family:

  • {:win32, :nt} - Windows NT-based systems (includes Windows XP, Vista, 7, 8, 10, 11, and Server editions)

We just need some simple heuristics for the userAgent string - probably looking for specific words/patterns would be enough. Check out the patterns from the Elixir ua_parser lib for ideas: ua_parser/priv/patterns.yml at main · beam-community/ua_parser · GitHub

For example, you could check if the userAgent contains “Mac”, “Win”, “Linux”, etc. and map those to the appropriate tuples. Should be pretty straightforward!

Re: consistency testing - it’s mostly impossible in this case since the Elixir version runs on the server (returning the server’s OS) while the JS version runs in the browser (returning the client’s OS).

So for this one:

  • Put your JS tests inside a “client-only behaviour” describe block in os_test.mjs and test various OS detections thoroughly there
  • In os_test.exs, just add a sanity test that calls the actual Erlang :os.type/0 function and asserts the returned value is one of the tuples we support on the client-side
tenkiller

tenkiller

UserAgent parsing is rather complicated if we rolled our own. Do you have a library recommendation, like ua-parser.js perhaps? Also, testing consistency with the JS implementation in os_test.exs is not clear to me either.

bartblast

bartblast OP

Creator of Hologram

@tenkiller For navigator, just swap out globalThis.navigator with a mock object in your tests:

describe("type/0", () => {
  let originalNavigator;

  beforeEach(() => {
    originalNavigator = globalThis.navigator;
  });

  afterEach(() => {
    globalThis.navigator = originalNavigator;
  });

  it("detects macOS via userAgentData", () => {
    globalThis.navigator = {
      userAgentData: {platform: "macOS"},
    };
    // test returns {:unix, :darwin}
  });

  it("detects Windows via userAgentData", () => {
    globalThis.navigator = {
      userAgentData: {platform: "Windows"},
    };
    // test returns {:win32, :nt}
  });

  it("detects Linux via userAgentData", () => {
    globalThis.navigator = {
      userAgentData: {platform: "Linux"},
    };
    // test returns {:unix, :linux}
  });

  it("falls back to platform when userAgentData unavailable", () => {
    globalThis.navigator = {
      userAgentData: undefined,
      platform: "MacIntel",
    };
    // test returns {:unix, :darwin}
  });

  it("falls back to userAgent when platform unavailable", () => {
    globalThis.navigator = {
      userAgentData: undefined,
      platform: "",
      userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
    };
    // test returns {:win32, :nt}
  });
});

For the implementation, the recommended fallback order is:

  1. navigator.userAgentData.platform - modern API with clean values (“macOS”, “Windows”, “Linux”)
  2. navigator.platform - legacy but widely supported (“MacIntel”, “Win32”, “Linux x86_64”)
  3. navigator.userAgent - last resort, requires string parsing

The userAgentData API isn’t supported in Firefox or Safari, so the platform fallback will handle those browsers. You may find that parsing userAgent is rarely needed in practice.

tenkiller

tenkiller

@bartblast Concerning os.type/0, I know you said you’d like it to mimic Erlang’s behavior and detect the OS platform and family via the navigator.userAgentData, but how should I actually test this? What’s the best strategy for stubbing the window.navigator object in os_test.mjs?

bartblast

bartblast OP

Creator of Hologram

Quick update: I’ve gone through all the remaining TODO Erlang functions and reviewed them. With far fewer left now, this was much more manageable!

I’ve pushed some functions to phase 2 after analyzing which Elixir stdlib functions depend on them. These deferred functions are primarily used by process-related Elixir stdlib functions, which aren’t targets for phase 1. Most of the deferred functions were ETS-related, commonly used by modules like PartitionSupervisor and Registry.

This is your last chance to contribute – only 40 functions left to port for phase 1!

Thanks for all your contributions so far!

bartblast

bartblast OP

Creator of Hologram

That’s intentional - there’s no separate primitive “string” type in Elixir at the VM level. Elixir strings are UTF-8 encoded binaries. Type.string() is only an internal helper Hologram uses for text segments inside bitstrings, not a public string type. You can use Type.bitstring() helper for text - it will convert the text to boxed bitstring type (which includes binaries and therefore UTF-8 strings).

bartblast

bartblast OP

Creator of Hologram

Looking at your PRs now, @Lucassifoni

tenkiller

tenkiller

I noticed that Type.encodeMapKey() does not support string data types. Is this intentional? I can extend it to support them in a separate PR, or would you rather I include that change in my upcoming :sets.to_list/1 PR?

For reference, here is the test I would add to type_test.mjs after extending it:

it("encodes boxed string value as map key", () => {
  const string = Type.string("Hologram");
  const result = Type.encodeMapKey(string);

  assert.equal(result, "string(Hologram)");
});

Where Next? Top

Trending in News & Updates Top

sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
kip
I’m a bit excited to announce that Localize and friends are now at release 1.0. Even though it’s a 1.0 release, it stands on 8 years of w...
New
bartblast
I’ll be using this thread to share Hologram patch release announcements. Minor releases will continue to get dedicated threads with blog ...
New
bartblast
Hologram v0.11 is out! Two headline features this release. First, Elixir regexes now run in the browser. They were server-only until now,...
New
nseaSeb
Just published search_ash 0.5.0 (with search_core 0.4.0) on Hex. What’s new: synonyms You can now declare a synonym dictionary per lang...
New
mudasobwa
MdexMultilineCells is an MDEx plugin enabling multi-line cells in Markdown tables with full inline/block Markdown rendering and automati...
New
sorenone
This release unifies configuration for queues, repos, and services, swaps opaque timing integers for readable durations, and backports pe...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews