npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@nartallax/parcel-packager-js-with-circular-dep-resolving

v2.8.2-alpha.8

Published

Packager plugin for Parcel that helps with proper resolution of circular dependencies

Downloads

15

Readme

Parcel packager that properly resolves circular dependencies

Did you ever had an error out of the blue that tells you that some imported value is undefined, or, worse, not present at all?
Did you then spend an hour to learn that it have something to do with circular dependencies and then spend a hour more to juggle your code around until error goes away... until next time?
This package aims to prevent this kind of stuff once and for all! At least it will try its best.

DISCLAIMER: this package is very hacky "fork" of original @parcel/packager-js. It may not work in some circumstances that I didn't take into account when writing the patch. Proceed with caution.

Install

Install the package:

npm install --save-dev @nartallax/parcel-packager-js-with-circular-dep-resolving

And put it into .parcelrc:

{
  "extends": "@parcel/config-default",
  "packagers": {
    "*.js": "@nartallax/parcel-packager-js-with-circular-dep-resolving"
  }
}

Done, now your bundles should be better.

But how does it work exactly?

This is kinda long story.

Let's start with understanding the problem properly

The problem is that the bundler needs to lay out your modules in a sequence. It's fine - until it discovers circular dependency.
Most of the bundlers (all of the popular ones, including default Parcel, webpack, requirejs and many more) don't think too much about how to resolve it. They just put SOME module to be evaluated before others, breaking the cycle of dependency. This means that this one module won't get a dependency at definition time.
This isn't a big deal - the dependency will be present later anyway - until that one module tries to use an imported value at definition time synchronously. Most common case of this error is extending a class. And boom - runtime error.

The thing is, in circle of dependencies there are "hot" dependencies (that means requiring module will use imported value synchronously) and "cold" dependencies (that means it won't). Hot dependencies must never be broken, as this will result in error; but it's fine to break cold dependencies. In most of dependency cycles there is at least one cold dependency; if there isn't - that means the code is very weird and that's probably a programmer's error.
So now we can see that default cyclic dependency resolution rule is "let's hope that this dependency is not hot", and that's very insufficient.

But what can we do?

We can go through one of two approaches to the problem.

Devmove: proxy approach

In devmode, modules are defined as functions, almost like the old good AMD. So the solution to the circular dependency problem is lazy loading - module is not loaded until its value is actually used. This is done by wrapping module definition in Proxy, and only calling to the actual definition when any of the Proxy's handlers are called.
This approach has a bunch of problems: it's a performance hit, and also it resolves every circular dependency; which is bad, because production approach works not so reliably; so, something that works fine in devmode can fail in production.

Production: reordering approach

The first step is to build dependency graph and drop everything that is not a part of the cycle.
Then we go over each dependency and check if it's cold. If it is - we cut it and drop everything that is not in cycle anymore; then we repeat the process until there's no cycles.

The interesting (and unreliable) part of how we determine if dependency is cold or hot - we analyse its code (going through its AST), trying to find which imported values it uses at definition time. You can imagine that there's a lot of cases that are not caught by this approach - but there's also a lot of simple cases that are caught, and that's usually sufficient. Even if it's not - you can always explicitly tell the packager that value is used by putting something like void myImportedValue in the module root.

When the graph is all de-cycled, we can easily build a sequence in which packages should be present in the bundle.

And how do we enforce that order?

This is also a fun part, like the one above!
We apply some hacks, introducing new imports to original code in proper order and limiting traversal of dependencies, so modules are substitute in required order.
Initially I tried to make a separate packager that wraps original @parcel/packager-js, but original is too complex to be non-invasively patched from outside.

And that's it.

Why this logic is not a part of most existing bundlers?

Looks like it's ECMAScript standard behaviour for this kind of things (which sucks).
If you're maintaining some bundler and want to adopt this logic - please do so. I'll be delighted to know that at least one bundler is better now.

Updating the fork to newer version of original

This part is for maintainers (me).
As you can see, this package is not a fork from git's point of view. It is forked by getting code of original package from npm and introducing some patches to it.
So, the update procedure is following:

  1. Backup lib directory
  2. Run scripts/pull_upstream.sh. This will substitute lib directory with newer version, as well as bring some additional files like package.json
  3. Search original lib directory for /** <PATCH> **/. Those are places that were patched. You need to copy the changes into new code (don't worry, there's like 5 or 7 of them).
  4. Test, it should be good now. And you may also delete your backup of lib, you won't need it anymore.
  5. Build, publish, whatever.