xstate: ESM loading fails for native Node.js@13.x.x loader

Description I’m trying to import xstate using Node@13 native support for ES Modules.

Expected Result Should be able to import as specified is the docs

Actual Result

The requested module ‘xstate’ does not provide an export named ‘Machine’

Reproduction

Simply run the following code after installing xstate and run it with Node >= 13.0.0

import { Machine } from 'xstate';

Additional context I’ve tried both setting the "type": "module" in my package.json and renaming the script file to *.mjs, but the error still persists.

Out of curiosity, I decided to use the import-star syntax to see what happened:

import * as xs from 'xstate';

console.log(xs);

It outputs:

[Module] {
  default: {
    matchesState: [Function: matchesState],
    mapState: [Function: mapState],
    StateNode: [Function: StateNode],
    State: [Function: State] {
      from: [Function (anonymous)],
      create: [Function (anonymous)],
      inert: [Function (anonymous)]
    },
    Machine: [Function: Machine],
    createMachine: [Function: createMachine],
    send: [Function: send],
    sendParent: [Function: sendParent],
    sendUpdate: [Function: sendUpdate],
    assign: [Function (anonymous)],
    doneInvoke: [Function: doneInvoke],
    forwardTo: [Function: forwardTo],
    interpret: [Function: interpret],
    Interpreter: [Function: Interpreter] {
      defaultOptions: [Object],
      interpret: [Function: interpret]
    },
    spawn: [Function: spawn],
    matchState: [Function: matchState],
    actions: {
      raise: [Function: raise],
      send: [Function: send],
      sendParent: [Function: sendParent],
      sendUpdate: [Function: sendUpdate],
      log: [Function: log],
      cancel: [Function (anonymous)],
      start: [Function: start],
      stop: [Function: stop],
      assign: [Function (anonymous)],
      after: [Function: after],
      done: [Function: done],
      respond: [Function: respond],
      forwardTo: [Function: forwardTo],
      escalate: [Function: escalate]
    },
    ActionTypes: {
      Start: 'xstate.start',
      Stop: 'xstate.stop',
      Raise: 'xstate.raise',
      Send: 'xstate.send',
      Cancel: 'xstate.cancel',
      NullEvent: '',
      Assign: 'xstate.assign',
      After: 'xstate.after',
      DoneState: 'done.state',
      DoneInvoke: 'done.invoke',
      Log: 'xstate.log',
      Init: 'xstate.init',
      Invoke: 'xstate.invoke',
      ErrorExecution: 'error.execution',
      ErrorCommunication: 'error.communication',
      ErrorPlatform: 'error.platform',
      ErrorCustom: 'xstate.error',
      Update: 'xstate.update',
      Pure: 'xstate.pure'
    },
    SpecialTargets: { Parent: '#_parent', Internal: '#_internal' }
  }
}

So it looks like even though state has an ESM target, it doesn’t seem it’s being properly loaded, because this looks like the CommonJS exported module.

For reference, here’s my package.json:

{
  "name": "xstate-labs",
  "version": "1.0.0",
  "module": "index.js",
  "license": "MIT",
  "type": "module",
  "dependencies": {
    "xstate": "^4.7.7"
  }
}

Tested with Node@13.7.0.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 18 (10 by maintainers)

Most upvoted comments

Just an update - we’ll be providing "exports" field in the future in the XState 5. It’s not yet implemented and nor v5 is ready to be shipped even as a prerelease but at least I have a plan now to make this happen. At this point in time, pkg.json#exports semantics seem to be pretty stable as webpack is going to ship support for it soon-ish, so it should be safe for us to start using that in the future.

.mjs extension might break some older tools that might work with XState today.

I feel super bad about issues like this - I understand the pain of the users trying to utilize this. I’ve wasted dozens of hours maneuvering through that ESM mess, trying to figure out a solution that would work everywhere, with every tool, for every package. This has been proven to be just impossible.

Some solutions work for some packages while they might not work for some other packages. Trying to figure out a perfect combination of things that can be done to address this in a particular package is exhausting and is always associated with a risk that we won’t get it right for the first time (for the record - I’ve been going through this with Nicolo from the Babel team and it took him like 5 patch releases of the @babel/runtime to land on something that has worked in all of the supported contexts. The situation with XState would probably be simpler but still…).

We also can’t just use the type: module or .mjs because that would make it impossible to require XState in node and there is surely code out there that needs this to work.

However, I think that we are only using named exports and currently node should be able to load CJS files with named exports from an ESM file - if only those named exports are declared in a certain way (to be statically analyzable). If you prepare a runnable repro case of the problem I might take a look at this to assess what’s going on. Note that this was not true at the beginning and there are node versions out there that can’t do this - but AFAIK all of the latest versions in any major line of node with ESM support should already be able to handle this.