<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jacob Smith</title>
    <description>The latest articles on DEV Community by Jacob Smith (@jakobjingleheimer).</description>
    <link>https://dev.to/jakobjingleheimer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F759955%2Fda00b6a1-9be6-4250-8868-bfe5ca2389c0.jpeg</url>
      <title>DEV Community: Jacob Smith</title>
      <link>https://dev.to/jakobjingleheimer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jakobjingleheimer"/>
    <language>en</language>
    <item>
      <title>The quest for auth in the modern web app</title>
      <dc:creator>Jacob Smith</dc:creator>
      <pubDate>Tue, 25 Oct 2022 08:18:55 +0000</pubDate>
      <link>https://dev.to/jakobjingleheimer/the-quest-for-auth-in-the-modern-web-app-3h9i</link>
      <guid>https://dev.to/jakobjingleheimer/the-quest-for-auth-in-the-modern-web-app-3h9i</guid>
      <description>&lt;p&gt;From ye old days of HTML table layouts, web apps have grown tremendously sophisticated, to paralleling native app capabilities and performance in, dare I say, most use-cases (push notifications on iOS aside, for now &lt;sup&gt;[1]&lt;/sup&gt;).&lt;/p&gt;

&lt;p&gt;There are a number of authentication methods available nowadays, but let's first review the main challenges common to them all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prologue
&lt;/h2&gt;

&lt;p&gt;A scene unfolds before you: a torrent of requests and responses flying back and forth to one place and another. Some venturing to the wide world, and some back to HQ. The ones back to HQ need the secret knock.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 1: Session
&lt;/h3&gt;

&lt;p&gt;You don't want just anybody wondering into HQ, so to prevent that you have a secret knock; but, you also need to prevent some rando figuring out your secret knock, so you decide to change it regularly. This is the essentials of basically any authentication setup.&lt;/p&gt;

&lt;p&gt;To mitigate the chances of a bad actor hijaking a user's active session, apps typically have a short-lived "access" token that expires after a few (~15) minutes. When that happens, most requests subsequently made to the app's server-side component(s) are rejected. This is an easily recoverable scenario. But what do you do when this happens?&lt;/p&gt;

&lt;p&gt;Apps typically have a second, "refresh", token. The only thing this token can be used to do is request a new access token. This refresh token is typically longer-lived, at a minimum, longer than the access token (otherwise, it's useless). When the refresh token expires, that is, by design, unrecoverable (without user intervention).&lt;/p&gt;

&lt;p&gt;Maybe you're thinking to yourself: "I got this". But suddenly, another token appears, and then another. "Where the heck are all these tokens coming from??"&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 2: Plot twist! (enter the multiverse)
&lt;/h3&gt;

&lt;p&gt;"Oh. my. god." You've realised users have your app running in multiple browser tabs (as many users do—the official term for these app instances is "clients").&lt;/p&gt;

&lt;p&gt;The door to the multiverse is thrown wide open: These tokens are coming from different browser tabs.&lt;/p&gt;

&lt;p&gt;This is actually classic concurrency. The issues here to face are most notably desynchronisation of shared data. In the case of authentication, that means the access and refresh tokens (the secret knock). If a client (an SPA running in a particular browser tab) keeps a copy of the tokens in its memory (eg &lt;code&gt;const tokens = { access: '…', refresh: '…' }&lt;/code&gt;), that can become stale when another tab is updated. This is a mistake that I see most of the time, and it will result in confusing bugs.&lt;/p&gt;

&lt;p&gt;The mitigation I see most often is keeping the tokens in a shared place and retrieve them on every request. This is, however, very inefficient, and depending on what shared place the tokens are put, it may not actually address the issue (ex putting them in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API"&gt;&lt;code&gt;IndexedDb&lt;/code&gt;&lt;/a&gt; re-creates the same problem, but now much more complicated).&lt;/p&gt;

&lt;h2&gt;
  
  
  Deus ex machina
&lt;/h2&gt;

&lt;p&gt;You could address the first challenge fairly easily in a number of ways, but the multiverse makes things much trickier. However, there is a solution that addresses all of these at the same time with basically &lt;strong&gt;no extra work&lt;/strong&gt;: A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"&gt;&lt;code&gt;ServiceWorker&lt;/code&gt;&lt;/a&gt;&lt;sup&gt;[2]&lt;/sup&gt; (which you may or may not already be using, eg for a &lt;a href="https://web.dev/progressive-web-apps/"&gt;PWA&lt;/a&gt;). The reason for this is because this is what it was intended to do: it creates a point of commonality for multi-client scenarios as a single ServiceWorker shared by all clients. I recently implemented this with &lt;a class="mentioned-user" href="https://dev.to/iainjreid"&gt;@iainjreid&lt;/a&gt; for &lt;a href="https://www.orbiit.ai"&gt;Orbiit&lt;/a&gt; (who donated this example): &lt;a href="https://www.github.com/JakobJingleheimer/webapp-auth-via-serviceworker"&gt;JakobJingleheimer/webapp-auth-via-serviceworker&lt;/a&gt; (verified on 24 Oct in the latest Chrome &lt;sup&gt;[3]&lt;/sup&gt;, Firefox, and iOS Safari).&lt;/p&gt;

&lt;p&gt;The important pieces to note are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API"&gt;WebStorage&lt;/a&gt; (specifically, &lt;code&gt;LocalStorage&lt;/code&gt;) because WebStorage is &lt;em&gt;synchronous&lt;/em&gt; and locking (multiple clients cannot write at the same time—reads and writes are queued). This is critical for precluding race conditions and desynchronisation.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;fetch&lt;/code&gt; event listener in the ServiceWorker to decorate &lt;em&gt;internal&lt;/em&gt; requests with authentication and handle the various session scenarios cited above.&lt;/li&gt;
&lt;li&gt;The ServiceWorker maintains the in-memory cache of tokens; this is safe because all roads lead to Rome (all requests go through the same ServiceWorker and its cache).&lt;/li&gt;
&lt;li&gt;The SPA is blocked from initialising until the ServiceWorker is ready. This is necessary to avoid any requests the SPA might make before the ServiceWorker can intercept them. The provided ServiceWorker is quite small (and it's important to keep it that way!), so the latency incurred waiting for it to activate is trivial (something like 3 ticks).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fetch quest
&lt;/h3&gt;

&lt;p&gt;The fetch handler in the provided example does a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ignores external requests (you don't want to tell the secret knock to some rando).&lt;/li&gt;
&lt;li&gt;Detects transient authentication failures in internal requests, automatically triggering a refresh (if possible), and then re-tries the original request.&lt;/li&gt;
&lt;li&gt;Detects and surfaces to the client unrecoverable authentication failures. This allows the client to consider any &lt;code&gt;401&lt;/code&gt; response a cue to tell the user to log in again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is one minor issue with the provided implementation, which is fortunately limited to DX: Handling a request doomed to fail. I think the most correct way to handle this scenario is to cancel it via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort"&gt;&lt;code&gt;AbortController::abort()&lt;/code&gt;&lt;/a&gt; because it causes the request in DevTools' Network panel to be marked &lt;code&gt;cancelled&lt;/code&gt;, which seems to me the most accurate representation of what has happened (the ServiceWorker has detected the request is doomed to fail and has cancelled it without hitting the network); this method also supports providing a reason. However, due to identical bugs in all major browsers &lt;sup&gt;[4][5][6]&lt;/sup&gt;, this does not work.&lt;/p&gt;

&lt;p&gt;This leaves 2 options that achieve the same end-result (but both create red herrings for the developer):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use AbortSignal and trigger a "network failure". This can look like an environment problem instead of expected behaviour.&lt;/li&gt;
&lt;li&gt;Return a fake failing response (eg &lt;code&gt;401&lt;/code&gt;). This looks like the server responded with the failure (when in fact the request never left and never touched the server); also, this option makes it more difficult for the client (SPA) to determine what to do (now it has no way to differentiate a real problem from one already being remediated).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I opted for the former (use AbortSignal despite the misleading "network failure") because it has fewer downsides, is a bit less misleading than the other option, and should just start working when the browser bugs are fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Epilogue
&lt;/h2&gt;

&lt;p&gt;This quest started ~4 years ago when I originally implemented it for another client. With the latest refinement (blocking the SPA loading until the ServiceWorker is ready), I believe I've addressed all the core concerns, but if you have any unaddressed, I'd love to hear about it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a&gt;&lt;/a&gt; Apple claim they "can't" support Push Notifications in PWAs for "performance reasons". Android and desktops somehow managed to address software performance issues years ago, so one might ask "Are Apple's engineers significantly less capable than everyone else?". We can be pretty confident Apple's engineers are not inept, so one might think the next plausible explanation is Apple are lying: It's a problem of &lt;em&gt;financial&lt;/em&gt; performance. Push Notifications are effectively a "must" for many apps, so this forces app developers to publish on the AppStore, which is hugely financially advantageous for Apple: Apple take a 30% cut of payments (which they have enforced as a requirement for inclusion in the AppStore). Fortunately, many governments around the world (ex &lt;a href="https://www.nytimes.com/2022/05/02/business/apple-pay-antitrust-eu.html"&gt;EU&lt;/a&gt;, &lt;a href="https://apnews.com/article/technology-business-south-korea-a8e160fb9b43681557445cfe06f25bc1"&gt;South Korea&lt;/a&gt;, &lt;a href="https://www.npr.org/2021/09/10/1036043886/apple-fortnite-epic-games-ruling-explained"&gt;US&lt;/a&gt;) have ruled Apple are violating anti-monopoly laws, and Apple must allow app developers to use the payment service of the developer's choice. So without financial or software performance issues, we might soon see this "performance reasons" issue disappear (unless Apple decide to vindictively keep up the ruse, and the world leaves them behind).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt; When working with a ServiceWorker, ensure you&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always use an Incognito window (stick to Chromium for dev—Firefox does not allow ServiceWorkers in Private mode aka an Incognito window)&lt;/li&gt;
&lt;li&gt;Enable DevTools → Application → Service Workers → &lt;code&gt;Update on reload&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you don't do both of these, you will have a very bad day.&lt;/p&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a&gt;&lt;/a&gt; &lt;a href="https://github.com/brave/brave-browser/issues/25855"&gt;A bug in Brave&lt;/a&gt; causes any ServiceWorker &lt;code&gt;fetch()&lt;/code&gt; of a CORS request to fail with &lt;code&gt;blocked:other&lt;/code&gt; (regardless of proper CORS setup). That means this solution does not &lt;em&gt;currently&lt;/em&gt; work at all in Brave (they're pretty quick to fix bugs).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a&gt;&lt;/a&gt; &lt;a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1377650"&gt;Chromium ServiceWorker fetch AbortSignal bug&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a&gt;&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1796998"&gt;Firefox ServiceWorker fetch AbortSignal bug&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a&gt;&lt;/a&gt; Safari just ignore bug reports, so I didn't bother.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>auth</category>
      <category>javascript</category>
      <category>pwa</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Custom ESM loaders: Who, what, when, where, why, how</title>
      <dc:creator>Jacob Smith</dc:creator>
      <pubDate>Tue, 12 Jul 2022 22:31:50 +0000</pubDate>
      <link>https://dev.to/jakobjingleheimer/custom-esm-loaders-who-what-when-where-why-how-4i1o</link>
      <guid>https://dev.to/jakobjingleheimer/custom-esm-loaders-who-what-when-where-why-how-4i1o</guid>
      <description>&lt;p&gt;Most people probably won't write their own custom ESM loaders, but using them could drastically simply your workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nodejs.org/api/esm.html#loaders"&gt;Custom loaders&lt;/a&gt; are a powerful mechanism for controlling an application, providing extensive control over loading modules—be that data, files, what-have-you. This article lays out real-world use-cases. End users will likely consume these via packages, but it could still be useful to know, and doing a small and simple one-off is very easy and could save you a lot of hassle with very little effort (most of the loaders I've seen/written are about 20 lines of code, many fewer).&lt;/p&gt;

&lt;p&gt;For prime-time usage, multiple loaders work in tandem in a process called "chaining"; it works like a promise chain (because it literally is a promise chain). Loaders are added via command-line in reverse order, following the pattern of its forebearer, &lt;a href="https://nodejs.org/api/cli.html#-r---require-module"&gt;&lt;code&gt;--require&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node &lt;span class="nt"&gt;--loader&lt;/span&gt; third.mjs &lt;span class="nt"&gt;--loader&lt;/span&gt; second.mjs &lt;span class="nt"&gt;--loader&lt;/span&gt; first.mjs app.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;node&lt;/code&gt; internally processes those loaders and then starts to load the app (&lt;code&gt;app.mjs&lt;/code&gt;). Whilst loading the app, &lt;code&gt;node&lt;/code&gt; invokes the loaders: &lt;code&gt;first.mjs&lt;/code&gt;, then &lt;code&gt;second.mjs&lt;/code&gt;, then &lt;code&gt;third.mjs&lt;/code&gt;. Those loaders can completely change basically everything within that process, from redirect to an entirely different file (even on a different device across a network) or quietly provide modified or entirely different contents of those file(s).&lt;/p&gt;

&lt;p&gt;In a contrived example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node &lt;span class="nt"&gt;--loader&lt;/span&gt; redirect.mjs app.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// redirect.mjs&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;specifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextResolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.prod.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.dev.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.test.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nextResolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will cause &lt;code&gt;node&lt;/code&gt; to dynamically load &lt;code&gt;app.dev.mjs&lt;/code&gt;, &lt;code&gt;app.test.mjs&lt;/code&gt;, or &lt;code&gt;app.prod.mjs&lt;/code&gt; based on the environment (instead of &lt;code&gt;app.mjs&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;However, the following provides a more robust and practical use-case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;   --loader typescript-loader \
   --loader css-loader \
   --loader network-loader \
   app.tsx
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useRoutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AppHeader&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./AppHeader.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AppFooter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./AppFooter.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/routes.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./global.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BrowserRouter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppHeader&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppFooter&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/BrowserRouter&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above presents quite a few items to address. Before loaders, one might reach for Webpack, which sits on top of Node.js. However, now, one can tap into &lt;code&gt;node&lt;/code&gt; directly to handle all of these on the fly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The TypeScript
&lt;/h2&gt;

&lt;p&gt;First up is &lt;code&gt;app.tsx&lt;/code&gt;, a TypeScript file: &lt;code&gt;node&lt;/code&gt; doesn't understand TypeScript. TypeScript brings a number of challenges, the first being the most simple and common: transpiling to javascript. The second is an obnoxious problem: TypeScript demands that import specifiers lie, pointing to files that don't exist. &lt;code&gt;node&lt;/code&gt; of course cannot load non-existent files, so you'd need to tell &lt;code&gt;node&lt;/code&gt; how to detect the lies and find the truth.&lt;/p&gt;

&lt;p&gt;You have a couple options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't lie. Use the &lt;code&gt;.ts&lt;/code&gt; etc extensions and use something like &lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt; in a loader you write yourself, or an off-the-shelf loader like &lt;a href="https://github.com/TypeStrong/ts-node#node-flags-and-other-tools"&gt;ts-node/esm&lt;/a&gt; to transpile the output. On top of being correct, this is also &lt;em&gt;significantly&lt;/em&gt; more performant. This is Node.js’s recommended approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: &lt;code&gt;tsc&lt;/code&gt; appears soon to support &lt;code&gt;.ts&lt;/code&gt; file extensions during type-checking: &lt;a href="https://github.com/microsoft/TypeScript/issues/37582"&gt;TypeScript#37582&lt;/a&gt;, so you'll hopefully be able to have your cake and eat it too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the wrong file extensions and guess (this will lead to decreased performance and possibly bugs).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Due to design decisions in TypeScript, there are unfortunately drawbacks in both options.&lt;/p&gt;

&lt;p&gt;If you want to write your own TypeScript loader, the Node.js Loaders team have put together a simple example: &lt;a href="https://github.com/nodejs/loaders-test/tree/HEAD/typescript-loader"&gt;nodejs/loaders-test/typescript-loader&lt;/a&gt;. &lt;code&gt;ts-node/esm&lt;/code&gt; would probably suit you better though.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CSS
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;node&lt;/code&gt; also does not understand CSS, so it needs a loader (&lt;code&gt;css-loader&lt;/code&gt; above) to parse it into some JSON-like structure. I use this most commonly when running tests, where styles themselves often don't matter (just the CSS classnames). So the loader I use for that merely exposes the classnames as simple, matching key-value pairs. I've found this to be sufficient as long as the UI is not actually drawn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.Container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.SomeInnerPiece&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./MyComponent.module.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// { Container: 'Container', SomeInnerPiece: 'SomeInnerPiece' }&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick-n-dirty example of &lt;code&gt;css-loader&lt;/code&gt; is available here: &lt;a href="https://github.com/JakobJingleheimer/demo-css-loader"&gt;JakobJingleheimer/demo-css-loader&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A Jest-like snapshot or similar consuming the classnames works perfectly fine and reflects real-world output. If you're manipulating the styles within your JavaScript, you'll need a more robust solution (which is still very feasible); however, this is maybe not the best choice. Depending on what you're doing, CSS Variables are likely better (and do not involve manipulating the styles at all).&lt;/p&gt;

&lt;h2&gt;
  
  
  The remote data (file)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;node&lt;/code&gt; does not yet fully support loading modules over a network (there is experimental support that is intentionally very restricted). It’s possible to instead facilitate this with a loader (&lt;code&gt;network-loader&lt;/code&gt; above). The Node.js Loaders team have put together a rudimentary example of this: &lt;a href="https://github.com/nodejs/loaders-test/tree/HEAD/https-loader"&gt;nodejs/loaders-test/https-loader&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  All together now
&lt;/h2&gt;

&lt;p&gt;If you have a "one-off" task to complete, like compiling your app to run tests against, this is all you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;   NODE_OPTIONS='--loader typescript-loader --loader css-loader --loader network-loader' \
   mocha \
   --extension '.spec.js' \
   './src'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As of this week, the team at &lt;a href="https://orbiit.ai"&gt;Orbiit.ai&lt;/a&gt; are using this as part of their development process, to a near 800% speed improvement to test runs. Their new setup isn't quite finished enough to share before &amp;amp; after metrics and some fancy screenshots, but I'll update this article as soon as they are.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"concurrently --kill-others-on-fail npm:test:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc --noEmit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_ENV=test NODE_OPTIONS='…' mocha --extension '…' './src'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:…"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see a similar working example in an open-source project here: &lt;a href="https://github.com/JakobJingleheimer/react-form5"&gt;JakobJingleheimer/react-form5&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For something long-lived (ex a dev server for local development), something like &lt;code&gt;esbuild&lt;/code&gt;'s &lt;a href="https://esbuild.github.io/api/#serve"&gt;&lt;code&gt;serve&lt;/code&gt;&lt;/a&gt; may better suit the need. If you're keen do it with custom loaders, you'll need a couple more pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple http server (JavaScript modules require it) using a dynamic import on the requested module.&lt;/li&gt;
&lt;li&gt;A cache-busting custom loader (for when the source code changes), such as &lt;a href="https://www.npmjs.com/package/quibble"&gt;quibble&lt;/a&gt; (who published an explanatory article on it &lt;a href="https://dev.to/giltayar/mock-all-you-want-supporting-es-modules-in-the-testdouble-js-mocking-library-3gh1"&gt;here&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;All in all, custom loaders are pretty neat. Try them out with today's v18.6.0 release of Node.js!&lt;/p&gt;

</description>
      <category>esm</category>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Configuring CommonJS &amp; ES Modules for Node.js</title>
      <dc:creator>Jacob Smith</dc:creator>
      <pubDate>Mon, 03 Jan 2022 20:06:22 +0000</pubDate>
      <link>https://dev.to/jakobjingleheimer/configuring-commonjs-es-modules-for-nodejs-12ed</link>
      <guid>https://dev.to/jakobjingleheimer/configuring-commonjs-es-modules-for-nodejs-12ed</guid>
      <description>&lt;p&gt;Configuration is always a chore, but an unfortunately necessary evil. And configuring a package for CommonJS (CJS) and ES Modules (ESM) can be a waking nightmare—not least because it has changed a dozen times in half as many years.&lt;/p&gt;

&lt;p&gt;As one of the implementers for &lt;a href="https://nodejs.org/api/esm.html#loaders"&gt;Node.js Loaders&lt;/a&gt;, touching much of Node’s internal ESM code, I get asked quite frequently “how do I make this work!?” (often with angry tears); but yet more frequently I come across packages that are just misconfigured.&lt;/p&gt;

&lt;p&gt;My name is Jacob, and I’m here to help.&lt;/p&gt;

&lt;p&gt;I have confirmed all the provided &lt;code&gt;package.json&lt;/code&gt; configurations (not specifically marked “does not work”) work in Node.js 12.22.x (v12 latest, the oldest supported line) and 17.2.0 (current latest at the time)&lt;sup&gt;1&lt;/sup&gt;, and for grins, with webpack 5.53.0 and 5.63.0 respectively. I’ve prepared a repository with them so you can check them out yourself: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples"&gt;JakobJingleheimer/nodejs-module-config-examples&lt;/a&gt; (the repo’s root README explains how to use it).&lt;/p&gt;

&lt;p&gt;For curious cats, Preamble: How did we get here and Down the rabbit-hole provide background and deeper explanations. If you're just looking for a solution, jump to Pick your poison for the TLDR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preamble: How did we get here
&lt;/h2&gt;

&lt;p&gt;CommonJS (CJS) was created &lt;em&gt;long&lt;/em&gt; before ECMAScript Modules (ESM), back when JavaScript was still adolescent—CJS and jQuery were created just 3 years apart. CJS is not an official (TC39) standard and is supported by a limited few platforms (most notably, Node.js). ESM as a standard has been incoming for several years; it is currently supported by all major platforms (browsers, Deno, Node.js, etc), meaning it will run pretty much everywhere. As it became clear ESM would effectively succeed CJS (which is still very popular and widespread), many attempted to adopt early on, often before a particular aspect of the ESM specification was finalised. Because of this, those changed over time as better information became available (often informed by learnings/experiences of those eager beavers), going from best-guess to the aligning with the specification.&lt;/p&gt;

&lt;p&gt;An additional complication is bundlers, which historically managed much of this territory. However, much of what we previously needed bundle(r)s to manage is now native functionality; yet bundlers are still (and likely always will be) necessary for some things. Unfortunately, functionality bundlers no-longer need to provide is deeply ingrained in older bundlers’ implementations, so they can at times be too helpful, and in some cases, anti-pattern (bundling a library is often not recommended by bundler authors themselves). The hows and whys of that are an article unto itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick your poison
&lt;/h2&gt;

&lt;p&gt;This article covers configuration of all possible combinations in modern Node.js (v12+). If you are trying to decide which options are ideal, it is better to avoid dual packages, so either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESM source and distribution&lt;/li&gt;
&lt;li&gt;CJS source and distribution with good/specific &lt;code&gt;module.exports&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You as a package author write&lt;/th&gt;
&lt;th&gt;Consumers of your package write their code in&lt;/th&gt;
&lt;th&gt;Your options&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CJS source code using &lt;code&gt;require()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CJS: consumers &lt;code&gt;require()&lt;/code&gt; your package&lt;/td&gt;
&lt;td&gt;CJS source and distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CJS source code using &lt;code&gt;require()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ESM: consumers &lt;code&gt;import&lt;/code&gt; your package&lt;/td&gt;
&lt;td&gt;CJS source and only ESM distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CJS source code using &lt;code&gt;require()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CJS &amp;amp; ESM: consumers either &lt;code&gt;require()&lt;/code&gt; or &lt;code&gt;import&lt;/code&gt; your package&lt;/td&gt;
&lt;td&gt;CJS source and both CJS &amp;amp; ESM distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESM source code using &lt;code&gt;import&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CJS: consumers &lt;code&gt;require()&lt;/code&gt; your package&lt;/td&gt;
&lt;td&gt;ESM source with only CJS distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESM source code using &lt;code&gt;import&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ESM: consumers &lt;code&gt;import&lt;/code&gt; your package&lt;/td&gt;
&lt;td&gt;ESM source and distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESM: source code uses &lt;code&gt;import&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CJS &amp;amp; ESM: consumers either &lt;code&gt;require()&lt;/code&gt; or &lt;code&gt;import&lt;/code&gt; your package&lt;/td&gt;
&lt;td&gt;ESM source and both CJS &amp;amp; ESM distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  CJS source and distribution
&lt;/h3&gt;

&lt;p&gt;This the "Rum &amp;amp; Coke" of packages: pretty difficult to mess up. Essentially just declare the package’s exports via the &lt;a href="https://nodejs.org/api/packages.html#conditional-exports"&gt;&lt;code&gt;"exports"&lt;/code&gt;&lt;/a&gt; field/field-set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/cjs-distro"&gt;cjs-with-cjs-distro&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;change&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;packageJson.exports["."] = filepath&lt;/code&gt; is shorthand for &lt;code&gt;packageJson.exports["."].default = filepath&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CJS source and only ESM distribution
&lt;/h3&gt;

&lt;p&gt;The "Gin &amp;amp; Tonic" of packages: This takes a small bit of finesse but is also pretty straight-forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/esm-distro"&gt;cjs-with-esm-distro&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;change&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CODE/ENTRYPOINT.mjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.mjs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://nodejs.org/api/esm.html#enabling"&gt;&lt;code&gt;.mjs&lt;/code&gt;&lt;/a&gt; file extension is a trump-card: it will override &lt;strong&gt;any&lt;/strong&gt; other configuration and the file will be treated as ESM. Using this file extension is necessary because &lt;code&gt;packageJson.exports.import&lt;/code&gt; does &lt;strong&gt;NOT&lt;/strong&gt; signify that the file is ESM (contrary to common, if not universal, misperception), only that it is the file to be used when the package is imported (ESM &lt;em&gt;can&lt;/em&gt; import CJS. See Gotchas below).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://nodejs.dev/learn/the-package-json-guide#engines"&gt;&lt;code&gt;"engines"&lt;/code&gt;&lt;/a&gt; field provides both a human-friendly and a machine-friendly indication of with which version(s) of Node.js the package is compatible. Depending on the package manager used, an exception may be thrown causing the installation to fail when the consumer is using an incompatible version of Node.js (which can be very helpful to consumers). Including this field here will save a lot of headache for consumers with an older version of Node.js who cannot use the package.&lt;/p&gt;

&lt;h3&gt;
  
  
  CJS source and both CJS &amp;amp; ESM distribution
&lt;/h3&gt;

&lt;p&gt;You have a few options:&lt;/p&gt;

&lt;h4&gt;
  
  
  Attach named exports directly onto &lt;code&gt;exports&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The "French 75" of packages: Classic but takes some sophistication and finesse.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller package weight&lt;/li&gt;
&lt;li&gt;Easy and simple (probably least effort if you don't mind keeping to a minor syntax stipulation)&lt;/li&gt;
&lt;li&gt;Precludes the Dual-Package Hazard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hacky-ish: Leverages non-explicitly documented behaviour in Node.js's algorithm (it &lt;em&gt;can&lt;/em&gt; but is very unlikely to change).&lt;/li&gt;
&lt;li&gt;Requires very specific syntax (either in source code and/or bundler gymnastics).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/dual/property-distro"&gt;cjs-with-dual-distro (properties)&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;change&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/cjs/index.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typically, you would see &lt;code&gt;module.exports&lt;/code&gt; assigned to something (be it an object or a function) like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="nx"&gt;qux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;someObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;qux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Use a simple ESM wrapper
&lt;/h4&gt;

&lt;p&gt;The "Piña Colada" of packages: Complicated setup and difficult to get the balance right.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller package weight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Likely requires complicated bundler gymnastics (I could not find any existing option to automate this in Webpack).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/dual/wrapper-distro"&gt;cjs-with-dual-distro (wrapper)&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;change&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/ESM-CODE/ENTRYPOINT.mjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/es/wrapper.mjs"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/cjs/index.js"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/cjs/index.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to support named exports from the CJS bundle for an ESM consumer, this will need a bit of gymnastics from a bundler but is conceptually very simple.&lt;/p&gt;

&lt;p&gt;In certain conditions, CJS exports an object (which gets aliased to ESM's &lt;code&gt;default&lt;/code&gt;); that object, like any object, is destructure-able. You can leverage that to pluck all the members of the object out, and then re-export them so the ESM consumer is none the wiser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./dist/es/wrapper.mjs&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cjs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../cjs/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cjs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Two full distributions
&lt;/h4&gt;

&lt;p&gt;The "Long Island Ice Tea" of packages: Chuck in a bunch of stuff and hope for the best. This is probably the most common and easiest of the CJS to CJS &amp;amp; ESM options, but you pay for it.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple bundler configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Larger package weight (basically double)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/dual/double-distro"&gt;cjs-with-dual-distro (double)&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;change&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/ESM-CODE/ENTRYPOINT.mjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/es/index.mjs"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/cjs/index.js"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/cjs/index.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ESM source and distribution
&lt;/h3&gt;

&lt;p&gt;The wine of packages: Simple, tried, and true.&lt;/p&gt;

&lt;p&gt;This is almost exactly the same as the CJS-CJS configuration above with 1 small difference: the &lt;a href="https://nodejs.org/api/packages.html#type"&gt;&lt;code&gt;"type"&lt;/code&gt;&lt;/a&gt; field. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/esm-distro"&gt;esm-with-esm-distro&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that ESM is not “backwards” compatible with CJS: a CJS module cannot &lt;code&gt;require()&lt;/code&gt; an ES Module; it is possible to use a dynamic import (&lt;code&gt;await import()&lt;/code&gt;), but this is likely not what consumers expect (and, unlike ESM, CJS does not support &lt;a href="https://github.com/tc39/proposal-top-level-await/"&gt;Top-Level Await&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  ESM source with only CJS distribution
&lt;/h3&gt;

&lt;p&gt;We're not in Kansas anymore, Toto.&lt;/p&gt;

&lt;p&gt;The configurations (there are 2 options) are nearly the same as ESM source and both CJS &amp;amp; ESM distribution, just exclude &lt;code&gt;packageJson.exports.import&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;💡 Using &lt;code&gt;"type": "module"&lt;/code&gt;&lt;sup&gt;2&lt;/sup&gt; paired with the &lt;code&gt;.cjs&lt;/code&gt; file extension (for commonjs files) yields best results. For more information on why, see Down the rabbit-hole and Gotchas below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/cjs-distro"&gt;esm-with-cjs-distro&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ESM source and both CJS &amp;amp; ESM distribution
&lt;/h3&gt;

&lt;p&gt;These are "mixologist" territory.&lt;/p&gt;

&lt;p&gt;When source code is written in non-JavaScript (ex TypeScript), options can be limited due to needing to use file extension(s) specific to that language (ex &lt;code&gt;.ts&lt;/code&gt;) and there is often no &lt;code&gt;.mjs&lt;/code&gt; equivalent&lt;sup&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Similar to CJS source and both CJS &amp;amp; ESM distribution, you have the same options.&lt;/p&gt;

&lt;p&gt;There is also a 4th option of publishing only an ESM distribution and forcing consumers to use a dynamic import (&lt;code&gt;await import()&lt;/code&gt;), but that is not quite the same and will likely lead to angry consumers, so it is not covered here.&lt;/p&gt;

&lt;h4&gt;
  
  
  Publish only a CJS distribution with property exports
&lt;/h4&gt;

&lt;p&gt;The "Mojito" of packages: Tricky to make and needs good ingredients.&lt;/p&gt;

&lt;p&gt;This option is almost identical to the CJS source with CJS &amp;amp; ESM distribution's property exports above. The only difference is in package.json: &lt;code&gt;"type": "module"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Only some build tools support generating this output. &lt;a href="https://www.rollupjs.org/"&gt;Rollup&lt;/a&gt; produces compatible output out of the box when targetting commonjs. Webpack as of &lt;a href="https://github.com/webpack/webpack/releases/tag/v5.66.0"&gt;v5.66.0+&lt;/a&gt; does with the new &lt;a href="https://webpack.js.org/configuration/output/#type-commonjs-static"&gt;&lt;code&gt;commonjs-static&lt;/code&gt;&lt;/a&gt; output type, (prior to this no commonjs options produces compatible output). It is not currently possible with &lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt; (which produces a non-static &lt;code&gt;exports&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The working example below was created prior to Webpack's recent release, so it uses Rollup (I'll get around to adding a Webpack option too).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/dual/property-distro"&gt;esm-with-cjs-distro&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.cjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.cjs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Using &lt;code&gt;"type": "module"&lt;/code&gt;&lt;sup&gt;2&lt;/sup&gt; paired with the &lt;code&gt;.cjs&lt;/code&gt; file extension (for commonjs files) yields best results. For more information on why, see Down the rabbit-hole and Gotchas below.&lt;/p&gt;

&lt;h4&gt;
  
  
  Publish a CJS distribution with an ESM wrapper
&lt;/h4&gt;

&lt;p&gt;The "Pornstar Martini" of packages: There's a lot going on here.&lt;/p&gt;

&lt;p&gt;This is also almost identical to the CJS source and dual distribution using an ESM wrapper, but with subtle differences &lt;code&gt;"type": "module"&lt;/code&gt; and some &lt;code&gt;.cjs&lt;/code&gt; file extenions in package.json.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/dual/wrapper-distro"&gt;esm-with-dual-distro (wrapper)&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/ESM-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/es/wrapper.js"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.cjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/cjs/index.cjs"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.cjs"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/cjs/index.cjs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Using &lt;code&gt;"type": "module"&lt;/code&gt;&lt;sup&gt;2&lt;/sup&gt; paired with the &lt;code&gt;.cjs&lt;/code&gt; file extension (for commonjs files) yields best results. For more information on why, see Down the rabbit-hole and Gotchas below.&lt;/p&gt;

&lt;h4&gt;
  
  
  Publish both full CJS &amp;amp; ESM distributions
&lt;/h4&gt;

&lt;p&gt;The "Tokyo Tea" of packages: Chuck in a bunch of stuff (with a surprise) and hope for the best. This is probably the most common and easiest of the ESM to CJS &amp;amp; ESM options, but you pay for it.&lt;/p&gt;

&lt;p&gt;In terms of package configuration, there are a few options that differ mostly in personal preference.&lt;/p&gt;

&lt;h5&gt;
  
  
  Mark the whole package as ESM and specifically mark the CJS exports as CJS via the &lt;code&gt;.cjs&lt;/code&gt; file extension
&lt;/h5&gt;

&lt;p&gt;This option has the least burden on development/developer experience.&lt;/p&gt;

&lt;p&gt;This also means that whatever build tooling must produce the distribution file with a &lt;code&gt;.cjs&lt;/code&gt; file extension. This might necessitate chaining multiple build tools or adding a subsequent step to move/rename the file to have the &lt;code&gt;.cjs&lt;/code&gt; file extension (ex &lt;code&gt;mv ./dist/index.js ./dist/index.cjs&lt;/code&gt;)&lt;sup&gt;3&lt;/sup&gt;. This can be worked around by adding a subsequent step to move/rename those outputted files (ex &lt;a href="https://rollupjs.org/"&gt;Rollup&lt;/a&gt; or &lt;a href="https://stackoverflow.com/q/21985492"&gt;a simple shell script&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Support for the &lt;code&gt;.cjs&lt;/code&gt; file extension was added in 12.0.0, and using it will cause ESM to properly recognised a file as commonjs (&lt;code&gt;import { foo } from './foo.cjs&lt;/code&gt; works). However, &lt;code&gt;require()&lt;/code&gt; does not auto-resolve &lt;code&gt;.cjs&lt;/code&gt; like it does for &lt;code&gt;.js&lt;/code&gt;, so file extension cannot be omitted as is commonplace in commonjs: &lt;code&gt;require('./foo')&lt;/code&gt; will fail, but &lt;code&gt;require('./foo.cjs')&lt;/code&gt; works. Using it in your package's exports has no drawbacks: &lt;code&gt;packageJson.exports&lt;/code&gt; (and &lt;code&gt;packageJson.main&lt;/code&gt;) requires a file extension regardless, and consumers reference your package by the &lt;code&gt;"name"&lt;/code&gt; field of your package.json (so they're blissfully unaware).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working example&lt;/strong&gt;: &lt;a href="https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/dual/double-distro"&gt;esm-with-dual-distro&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=12.22.7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/ESM-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/es/index.js"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.cjs"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.cjs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;importable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Using &lt;code&gt;"type": "module"&lt;/code&gt;&lt;sup&gt;2&lt;/sup&gt; paired with the &lt;code&gt;.cjs&lt;/code&gt; file extension (for commonjs files) yields best results. For more information on why, see Down the rabbit-hole and Gotchas below.&lt;/p&gt;

&lt;h5&gt;
  
  
  Use the &lt;code&gt;.mjs&lt;/code&gt; (or equivalent) file extension for all source code files
&lt;/h5&gt;

&lt;p&gt;The configuration for this is the same as CJS source and both CJS &amp;amp; ESM distribution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-JavaScript source code&lt;/strong&gt;: The non-JavaScript language’s own configuration needs to recognise/specify that the input files are ESM.&lt;/p&gt;

&lt;h4&gt;
  
  
  Node.js before 12.22.x
&lt;/h4&gt;

&lt;p&gt;🛑 You should not do this: Versions of Node.js prior to 12.x are End of Life and are now vulnerable to serious security exploits.&lt;/p&gt;

&lt;p&gt;If you're a security researcher needing to investigate Node.js prior to v12.22.x, feel free to contact me for help configuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Down the rabbit-hole
&lt;/h2&gt;

&lt;p&gt;Specifically in relation to Node.js, there are 4 problems to solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Determining format of source code files (author running her/his own code)&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Determining format of distribution files (code consumers will receive)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publicising distribution code for when it is &lt;code&gt;require()&lt;/code&gt;’d (consumer expects CJS)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publicising distribution code for when it is &lt;code&gt;import&lt;/code&gt;’d (consumer probably wants ESM)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ The first 2 are &lt;strong&gt;independent&lt;/strong&gt; of the last 2.&lt;/p&gt;

&lt;p&gt;The method of loading does NOT determine the format the file is interpreted as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;package.json’s&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;exports.require&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;≠&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;CJS&lt;/code&gt;&lt;/strong&gt;. &lt;code&gt;require()&lt;/code&gt; does NOT and cannot blindly interpret the file as CJS; for instance, &lt;code&gt;require('foo.json')&lt;/code&gt; correctly interprets the file as JSON, not CJS. The module containing the &lt;code&gt;require()&lt;/code&gt; call of course must be CJS, but what it is loading is not necessarily also CJS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;package.json’s&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;exports.import&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;≠&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;ESM&lt;/code&gt;&lt;/strong&gt;. &lt;code&gt;import&lt;/code&gt; similarly does NOT and cannot blindly interpret the file as ESM; &lt;code&gt;import&lt;/code&gt; can load CJS, JSON, and WASM, as well as ESM. The module containing the &lt;code&gt;import&lt;/code&gt; statement of course must be ESM, but what it is loading is not necessarily also ESM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when you see configuration options citing or named with &lt;code&gt;require&lt;/code&gt; or &lt;code&gt;import&lt;/code&gt;, resist the urge to assume they are for &lt;em&gt;determining&lt;/em&gt; CJS vs ES Modules.&lt;/p&gt;

&lt;p&gt;⚠️ Adding an &lt;code&gt;"exports"&lt;/code&gt; field/field-set to a package’s configuration effectively &lt;a href="https://nodejs.org/api/packages.html#package-entry-points"&gt;blocks deep pathing into the package&lt;/a&gt; for anything not explicitly listed in the exports’ subpathing. This means it can be a breaking change.&lt;/p&gt;

&lt;p&gt;⚠️ Consider carefully whether to distribute both CJS and ESM: It creates the potential for the &lt;a href="https://nodejs.org/api/packages.html#dual-package-hazard"&gt;Dual Package Hazard&lt;/a&gt; (especially if misconfigured and the consumer tries to get clever). This can lead to an extremely confusing bug in consuming projects, especially when your package is not perfectly configured. Consumers can even be blind-sided by an intermediary package that uses the "other" format of your package (eg consumer uses the ESM distribution, and some other package the consumer is also using itself uses the CJS distribution). If your package is in any way stateful, consuming both the CJS and ESM distributions will result in parallel states (which is almost surely unintentional).&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;package.json&lt;/code&gt;'s &lt;code&gt;"type"&lt;/code&gt; field changes the &lt;code&gt;.js&lt;/code&gt; file extension to mean either &lt;code&gt;commonjs&lt;/code&gt; or ES &lt;code&gt;module&lt;/code&gt; respectively. It is very common in dual/mixed packages (that contain both CJS and ESM) to use this field incorrectly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;⚠️&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;THIS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;DOES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;WORK&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/ESM-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not work because &lt;code&gt;"type": "module"&lt;/code&gt; causes &lt;code&gt;packageJson.main&lt;/code&gt;, &lt;code&gt;packageJson.exports["."].require&lt;/code&gt;, and &lt;code&gt;packageJson.exports["."].default&lt;/code&gt; to get interpreted as ESM (but they’re actually CJS).&lt;/p&gt;

&lt;p&gt;Excluding &lt;code&gt;"type": "module"&lt;/code&gt; produces the opposite problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;⚠️&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;THIS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;DOES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;WORK&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/ESM-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATH/TO/DIST/CJS-CODE/ENTRYPOINT.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not work because &lt;code&gt;packageJson.exports["."].import&lt;/code&gt; will get interpreted as CJS (but it’s actually ESM).&lt;/p&gt;

&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;There was a bug in Node.js v13.0–13.6 where &lt;code&gt;packageJson.exports["."]&lt;/code&gt; had to be an array with verbose config options as the first item (as an object) and the “default” as the second item (as a string). See &lt;a href="https://github.com/nodejs/modules/issues/446"&gt;nodejs/modules#446&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;"type"&lt;/code&gt; field in package.json changes what the &lt;code&gt;.js&lt;/code&gt; file extension means, similar to to an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type"&gt;HTML script element’s type attribute&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;TypeScript has experimental support for the &lt;a href="https://www.typescriptlang.org/docs/handbook/esm-node.html#type-in-packagejson-and-new-extensions"&gt;package.json &lt;code&gt;"type"&lt;/code&gt; field&lt;/a&gt; and &lt;a href="https://www.typescriptlang.org/docs/handbook/esm-node.html#new-file-extensions"&gt;&lt;code&gt;.cts&lt;/code&gt; and &lt;code&gt;.mts&lt;/code&gt; file extensions&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Thank you to &lt;a class="mentioned-user" href="https://dev.to/geoffreybooth"&gt;@geoffreybooth&lt;/a&gt;, &lt;a href="https://www.github.com/guybedford"&gt;@guybedford&lt;/a&gt;, &lt;a class="mentioned-user" href="https://dev.to/ljharb"&gt;@ljharb&lt;/a&gt;, &lt;a class="mentioned-user" href="https://dev.to/jwfwessels"&gt;@jwfwessels&lt;/a&gt;, and &lt;a href="https://www.github.com/sokra"&gt;@sokra&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
