reaflow: `require() of ES Module` error in Next.js

I’m submitting a…

[x] Bug report

Current behavior

Unable to use reaflow with the current version of Next.js. I have started a Next.js project using npx create-next-app. After adding reaflow as a dependency and importing it in /pages/index.js, I get the following error in my browser:

Error: require() of ES Module /Users/.../node_modules/p-cancelable/index.js from /Users/.../node_modules/reaflow/dist/index.cjs.js not supported.
Instead change the require of index.js in /Users/.../node_modules/reaflow/dist/index.cjs.js to a dynamic import() which is available in all CommonJS modules.

I tried patching this by overriding the p-cancelable library to it’s previous 2.x version – before its switch to ES modules only in v3 – but this caused another, similar error from another dependency.

Expected behavior

Ideally the library will work as expected on import.

Minimal reproduction of the problem with instructions

Start a new Next.js project (12.0.7) and install reaflow as a dep. Try to import and use { Canvas } in /pages/index.js.

Environment


Libs:
- react version: 17.0.2
- next version: 12.0.7
- reaflow version: ^4.2.15

Browser:
- [x] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 16.12.0, 14.18.1
- Platform:  macOS

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 9
  • Comments: 15 (5 by maintainers)

Most upvoted comments

I was able to run this package by installing a plugin and making two changes in the next.config.js file.

npm i -s next-transpile-modules
// next.config.js
const withTM = require('next-transpile-modules')(['reaflow']);  // first import this plugin with reaflow as a target

module.exports = withTM({
   ...previousConfig,
   experimental: {
    esmExternals: 'loose' // second add this experimental flag to the config
  }
})

I should note though this is my first time using reaflow and I’ve just about run the simplest two node example. So I don’t know if it’ll continue to work with more things added. Plus I don’t mind using experimental flags in the current repository.

This is the actual component I’m running

import { Canvas } from 'reaflow';

const Page = () => (
  <div style={{ position: 'relative', width: '100vw', height: '100vh' }}>
    <div
      style={{
        position: 'absolute',
        top: 0,
        bottom: 0,
        left: 0,
        right: 0,
        backgroundColor: '#F5F5F5'
      }}
    >
      {
        // Don't render the Canvas on the server
        typeof window !== 'undefined' && (
          <Canvas
            maxWidth={800} // Use small canvas dimensions to make sure the nodes appear on screen immediately
            maxHeight={600}
            nodes={[
              {
                id: '1',
                text: '1'
              },
              {
                id: '2',
                text: '2'
              }
            ]}
            edges={[
              {
                id: '1-2',
                from: '1',
                to: '2'
              }
            ]}
          />
        )
      }
    </div>
  </div>
);

export default Page;

@amcdnl my two cents…

  • A lot of packages (including p-cancellable) are adopting a «ESM-only» strategy. They don’t bother with CJS, they simply set type:module in the package.json and include .js files which are valid ES modules in the package.
  • That is not really a problem anymore, all LTS Node.js versions and browsers support ESM, and packages with type:module are well understood by modern bundlers.
  • This approach is «infectious»: If you have a dependency that’s ESM-only, you have to be ESM-only too
    • Well, not strictly. You can import CJS from ESM, but only via import() expression. But the import() expression is async, it returns a Promise, so you can’t use it at the top-level.

My suggestion would be to:

  • Set type:module in package.json
  • Drop CJS and provide only ESM (~ if you set type:module then the files can keep the .js extension)
  • Adopt package.json exports
{
  "name": "reaflow",
  "type": "module",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.esm.js"
      }
    }
  }
}

(plus the other stuff, but drop main and module)

TypeScript 4.7 has added support for package.json exports: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-beta/#package-json-exports-imports-and-self-referencing

This lib not support SSR, so I used dynamic import same @freyandhy but for functions need to import with other way

example import upsertNode
const { upsertNode } = await import(“reaflow”);

<Canvas
  nodes={nodes}
  edges={edges}
  edge={
    <Edge
      add={<Add hidden={false} />}
      onAdd={async (event, edge) => {
        const id = `node-${Math.random()}`;
        const newNode = {
          id,
          text: id,
        };

        const { upsertNode } = await import("reaflow");

        const results = upsertNode(nodes, edges, edge, newNode);

        setNodes(results.nodes);
        setEdges(results.edges);
      }}
    />
  }
  onLayoutChange={(layout) => console.log("Layout", layout)}
/>;

If your build fails, try to add types to dynamic import like the code below

const Canvas: React.ComponentType<CanvasContainerProps> = dynamic(
    () =>
        import("reaflow").then((mod) => {
            return mod.Canvas as any;
        }),
    { loading: () => <p>loading</p>, ssr: false },
);

const Node: React.ComponentType<NodeProps> = dynamic(
    () => import("reaflow").then((mod) => mod.Node as any),
    {
        ssr: false,
    },
);

import type { Edge as EdgeType } from "reaflow";

type EdgePropType = JSX.LibraryManagedAttributes<
    typeof EdgeType,
    React.ComponentProps<typeof EdgeType>
>;

const EdgeReaflow: React.ComponentType<EdgePropType> = dynamic(
    () => import("reaflow").then((mod) => mod.Edge as any),
    {
        ssr: false,
    },
);

const Add: React.ComponentType<Partial<AddProps>> = dynamic(
    () => import("reaflow").then((mod) => mod.Add as any),
    {
        ssr: false,
    },
);

@amcdnl will there be a fix for that?

I use Dynamic Import to import the modules. See the example at codesandbox here