Full Width [alt+shift+f] FOCUS MODE Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
47
Oscar, an open-source contributor agent architecture - Oscar is a project from the Go community that aims to develop AI agents that assist open source maintainers. I think this is a great idea. Open source is a load-bearing pillar in our modern digital infrastructure and maintainers need help. If AI agents were able to reduce the maintenance burden, it could reduce burnout, improve project longevity, and encourage new development. Excited to see where this goes. Move on to ESM-only - A proper update on JavaScript’s move-to-ESM fiasco. I was pleasantly surprised to learn that tools like Vite are helping push the community migration forward. One clear takeaway is that new packages should publish in ESM-only (no dual-publishing of ESM + CommonJS). Last week, I ended up republishing Checkboxland as ESM-only, in large part due to the influence of this post. My keyboard ergonomics journey as an engineer - A nice write-up on keyboard ergonomics from my former co-worker Grant. This post...
4 months ago

Comments

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Bryan Braun - Blog

Breaking down circular dependencies in JavaScript

I write a lot of JavaScript but circular dependencies have always been kind of a mystery to me. The error messages always seem random and inscrutable, and sometimes there’s no error message at all! I wanted to understand this topic better, so I ran a series of experiments and wanted to share what I learned. Let’s break down circular dependencies in JavaScript. What are circular dependencies? Circular dependencies happen when your JavaScript import statements result in a loop: The loop can consist of two files, three files, or more. Any time your import statements create a loop like this, there’s a risk that your code won’t work as expected. How do you know when you have circular dependencies? There’s no easy way built into the language! In JavaScript, a circular dependency often manifests as a seemingly unrelated error (like a ReferenceError or TypeError). This is different from many other programming languages, which often tell you directly that your imports are bad: Python: ImportError Go: import cycle not allowed So why can’t JavaScript come out and say ⚠️ CircularDependencyError? It’s because JavaScript modules are designed to be loaded and executed on-the-fly. When your browser loads a web page and starts executing its first JavaScript file, it has no idea how many more files are still coming. Those files could still be sitting on servers on the other side of the world. This is a very different situation than a Go or Python program, where the import system can analyze the whole dependency tree before executing a single line of code. Stepping through a circular dependency in JavaScript The best way to explain the errors that JavaScript gives us is to step through a circular dependency scenario: Click to view a larger version of this image. Here’s what we see on each step: Step 1: On line 1 of index.js, execution pauses to download a.js so its value a can be imported. Step 2: Upon downloading a.js, execution continues in a.js but pauses on line 1 to download b.js, so its value b can be imported. Step 3: Upon downloading b.js, execution continues in b.js and finds an import on line 1 pointing back at a.js (a circular import). Step 4: a.js is already downloaded, but it has no exports defined because we haven’t executed anything past line 1 in a.js at this point. Thus, we cannot fulfill the import in b.js. Step 5: Execution continues in b.js with a remaining uninitialized. When a is called on line 3, the program errors with: ReferenceError: Cannot access 'a' before initialization. To summarize, the circular dependency results in code being executed with uninitialized values. This could result in various errors, like the ReferenceError above. Why do circular dependencies sometimes not cause errors? JavaScript’s imports are described as “Live Bindings.” This means that the imported value can start out uninitialized (due to circular dependencies) and become fully useable once the rest of code has been evaluated. In other words, some circular dependencies are error-free because they “work themselves out” before you call the affected code. I once worked in a code-base that was chock full of circular imports but none of them ever caused any issues. Why? It’s because all the code was defined in functions, none of which would be called until after everything was loaded. To demonstrate, we can update the last scenario to work in a similar way: Click to view a larger version of this image. Steps 1-4 are the same as above but things start to change at step 5: Step 5: a remains uninitialized but instead of being called directly, it is placed in a function definition (no error). Step 6: With b.js completed, execution in a.js continues down to line 3, which defines the export for a. From this point on, any code calling a will get an initialized value, as a result of the live bindings. Step 7: We call a() successfully, which in-turn calls b(). Ultimately, all the code gets called with no errors. To summarize, by the time we actually call that “uninitialized a”, the live bindings have updated its value and it’s no longer uninitialized. We’re safe because the value of a is only retrieved when the variable is actually used. Now, I wouldn’t recommend this as a way of fixing dependency issues. I’d feel better about removing the circular dependencies altogether. Still, I’ll bet there are plenty of production apps with circular dependencies that currently rely on this behavior. Preventing circular dependencies While JavaScript may not have built-in circular dependency checking, we still have options for preventing these issues. 3rd-party tools like madge and eslint-plugin-import can perform static analysis on your JavaScript codebase and detect circular dependencies before they become unwieldy. Some monorepo tools like NX and Rush have similar features built-in to their workflows. Of course, the best prevention is a well-organized codebase, with a clear hierarchy for shared code. What about Node / Bun / Webpack / etc? The examples I shared above are focused on the “ES modules in the browser” use-case, but JavaScript runs in a lot of different contexts. Server-side JavaScript doesn’t need to download its source over the network (making it more like Python) and bundling tools like Webpack can combine all the code into a single file. Are circular dependencies an issue in these scenarios? In short, yes. In my experiments, I was surprised to find that the error outcomes for browser, server, and bundler were basically the same. For example, with Webpack, the import statements were removed but the combined code still produced the same error: // b.js console.log('b.js:', a); // ReferenceError: Cannot access 'a' before initialization const b = 'B'; // a.js console.log('a.js:', b); const a = 'A'; I should also mention that while Node.js produced the same error when using the import syntax (ESM), it behaved differently when using the require syntax (CommonJS): $ node node-entry.cjs (node:13010) Warning: Accessing non-existent property 'Symbol(nodejs.util.inspect.custom)' of module exports inside circular dependency (Use `node --trace-warnings ...` to show where the warning was created) (node:13010) Warning: Accessing non-existent property 'constructor' of module exports inside circular dependency (node:13010) Warning: Accessing non-existent property 'Symbol(Symbol.toStringTag)' of module exports inside circular dependency It's nice that the warnings say "circular dependency" explicitly, when using CommonJS. This makes sense when you consider that CommonJS is an entirely different import system that doesn’t conform to the ECMAScript Modules spec. Comparing the two is comparing apples and oranges! Conclusion Circular dependencies can be confusing but it makes a lot more sense when you walk through the scenarios step by step. As always, nothing beats an experiment for getting a clear understanding of something like this. If you want a closer look at my test results, feel free to check out the repo.

5 months ago 52 votes
Technology you don’t have to think about

“Civilization advances by extending the number of important operations which we can perform without thinking about them.” – Alfred North Whitehead Effective technology takes our most time-consuming tasks and moves them into the background so we can focus on other important things. We can see this happening on the civilization level with the infrastructure we’ve built to get inexpensive food, water, energy, and transportation. But it also works on the personal level. Whether it’s a high-quality dishwasher or well-designed personal-finance software, technology is our primary tool for saving time and redirecting our attention. But not all technology is successful at this. The power of technology is abstraction and a poor abstraction is worse than having no technology at all. Case in point: a smart light-bulb that never saves you enough time to recover the time you spent setting it up. Simon Sarris has a great post describing some of these issues: “Many modern devices (and apps) really excel at squishing tradeoffs into weird shapes. They are better thought of as little imps that sneak into homes and ask for more and more of your attention. They want to gently claw at your eyes and ears. They want to put notifications on your phone and remind you that you need to interact with them, or buy more of them, so that they might become even more convenient.” Simon Sarris, Careful Technology Much of our technology has small hidden costs. A new app on your phone, an audible hum in the background, a recommended cleaning every six months, recurring manual software updates, monthly emails in your inbox, increased risk of a breaker trip, reduced counter-top space, parts that need replaced, a small monthly fee, a new username and password, batteries to recharge, parts to recycle, etc, etc. Each cost seems small but with enough bad technology you face death by a thousand cuts. You lose time, peace, and other more difficult-to-quantify things, like “the cozier feeling of home.” We have to be discerning about the technologies we let into our lives. I’m tired of technology that trades one set of problems for another. I want that technology you don’t have to think about.

5 months ago 43 votes
You can’t fix a problem you don’t understand

A few weeks ago, I was building a server-side API client. I had written the code and tested it in isolation. Everything looked good. Unfortunately, when I included it in the main service, I started seeing errors. I decided to try asking an AI tool for suggestions. I gave it the error message and a bunch of context. It gave me a solution with a detailed explanation. The errors went away. But the solution didn’t sit right with me. It was a bit complex, introducing more layers of code and various protections. The errors were gone, but I couldn’t clearly explain why it worked, and that was bothering me. While the code was being reviewed, I decided to take another look. I brought back the error and spent some time digging into the stack trace. That’s when I made the discovery: it was an environment issue. All I needed to do was set an environment variable and the issue would be fixed. The AI-provided code had been masking the real issue, quietly suppressing the error, and hiding the truth in its complexity. Now this is the part of the post where I’m supposed to criticize AI programming tools. I won’t be doing that. This isn’t an AI problem. I remember the first time I tried to fix a memory leak. Certain iPhone users would load the webpage, interact for a while, and then randomly the webpage would crash. We struggled to diagnose the issue (Safari’s devtools weren’t great at the time). We thought we fixed it several times but the issue kept coming back. Why? Because we didn’t understand the problem. We kept digging and eventually we found it: one of our dependencies was storing massive amounts of data on the window object and it wasn’t getting cleaned up. We added a cleanup step and the problem was gone for good. Since then, I’ve adopted a mantra: you can’t fix a problem you don’t understand. It doesn’t matter if the “fix” comes from AI, Stack Overflow, or trial-and-error. If I don’t understand the problem, I feel unsettled until I do.

6 months ago 51 votes
Made in 2024

Here are some things I made in 2024: Music Box Fun: Advanced Editing (a new major feature): Adds multiple-note selection for bulk operations on notes (like deletion, copy/paste, nudging and dragging) Also includes a “space editor” for arbitrarily adding/removing space anywhere in the song Includes other niceties like note highligting during playback and pitch highlight on hover Music Box Fun songs I made: Elliott’s Theme (Stardew Valley) in 15-note and 30-note versions Bluey Theme Song Jupiter Theme (The Planets) 11 new projects added to Let’s Get Creative, now available at a new domain name: https://letsgetcreative.today The Firefly Building in Minecraft (if you know, you know) 13 blog posts on bryanbraun.com, including a companion repo to my post on unusual Git IDs. I’m happy with this list. It was a year of many challenges. In February we got hit by a flooded basement and an emergency hospitalization which left my wife with limited mobility for a month. It was a difficult time and I’ll be forever grateful for the family and friends who helped us get through it. It was also a year of growth for my kids in particular, bringing many new parenting challenges. At the same time, I have so much to be grateful for. My wife and I hit our fifteenth anniversary and our relationship has grown stronger despite (or perhaps because of) the storms we’ve weathered. Every year brings new opportunities and challenges and it’s a privilege to have a committed partner that I can face them with. 🚵🏻‍♀️🚵🏻‍♂️ I wish you all the very best in 2025.

8 months ago 75 votes

More in technology

Turtle bots, Gestalt principles, and emergent art

In the worlds of programming and robotics, turtles are entities — either virtual or physical robots— that follow commands to move around a 2D plane. Those are usually very simple commands, such as “move forward 10 units” or “rotate 90 degrees clockwise,” and they help people learn some programming fundamentals (like Logo in the ’80s!) […] The post Turtle bots, Gestalt principles, and emergent art appeared first on Arduino Blog.

yesterday 5 votes
Microsoft makes 6502 BASIC open source

It was probably going to happen sooner or later, but Microsoft has officially released the source code for 6502 BASIC. The specific revision is very Commodore-centric: it's the 1977 "8K" BASIC variant "1.1," which Commodore users know better as BASIC V2.0, the same BASIC used in the early PET and with later spot changes from Commodore (including removing Bill Gates' famous Easter egg) in the VIC-20 and Commodore 64. I put "8K" in quotes because the 40-bit Microsoft Binary Format version, which is most familiar as the native floating point format for most 8-bit BASICs derived from Microsoft's and all Commodore BASICs from the PET on up, actually starts at 9K in size. In the C64, because there is RAM and I/O between the BASIC ROM and the Kernal ROM, there is an extra JMP at the end of the BASIC ROM to continue to the routine in the lowest portions of the Kernal ROM. The jump doesn't exist in the VIC-20 where the ROM is contiguous and as a result everything past that point is shifted by three bytes on the C64, the length of the instruction. This is, of course, the same BASIC that Gates wanted a percentage of but Jack Tramiel famously refused to budge on the $25,000 one-time fee, claiming "I'm already married." Gates yielded to Tramiel, as most people did then, but I suspect the slight was never forgotten. Not until the 128 did Microsoft officially appear in the credits for Commodore BASIC, and then likely only as a way to push its bona fides as a low-end business computer. Microsoft's source release also includes changes from Commodore's own John Feagans, who rewrote the garbage collection routine, and was the original developer of the Commodore Kernal and later Magic Desk. The source code is all in one big file (typical for the time) and supports six machine models, the first most likely a vapourware 6502 system never finished by Canadian company Semi-Tech Microelectronics (STM) better known for the CP/M-based Pied Piper, then the Apple II, the Commodore (in this case PET 2001), the Ohio Scientific (OSI) Challenger, the Commodore/MOS KIM-1, and most intriguingly a PDP-10-based simulator written by Paul Allen. The source code, in fact, was cross-assembled on a PDP-10 using MACRO-10, and when assembled for the PDP-10 emulator it actually emits a PDP-10 executable that traps on every instruction into the simulator linked with it — an interesting way of effectively accomplishing threaded code. A similar setup was used for their 8080 emulator. Unfortunately, I don't believe Allen's code has been released anywhere, though I'd love to be proven wrong if people know otherwise. Note that they presently don't even mention the STM port in the Github README, possibly because no one was sure what it did. While MACRO-10 source for 6502 BASIC has circulated before and been analysed in detail, most notably by Michael Steil, this is nevertheless the first official release where it is truly open-source under the MIT license and Microsoft should be commended for doing so. This also makes it much easier to pull a BASIC up for your own 6502 homebrew system — there's nothing like the original.

3 days ago 12 votes
Byte Interviews IBM's Philip D. Estridge (1983)

They dig into the IBM PC.

5 days ago 12 votes
The QNX Operating System

Quantum Software and the microkernel UNIX

6 days ago 14 votes
ArtSci MagiCalc

The Visual Difference. See for yourself.

a week ago 19 votes