Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESM import in ESM modules results into TypeError: (0 , import_....default) is not a function #2480

Open
CSchulz opened this issue Aug 17, 2022 · 10 comments

Comments

@CSchulz
Copy link

CSchulz commented Aug 17, 2022

I have issues with ESM imports in ESM modules. I am using esbuild@0.13.23 but also esbuild@0.15.5 fails.

I think it could be related to #2384 and #2026.

lodash-es/isPlainObject is a .js file.

TypeError: (0 , import_isPlainObject.default) is not a function
    at isPlainObject (commons-testing\fesm2015\commons-testing.mjs:185:10)

Here are the specific file parts:

Original mjs file

  import isPlainObject from 'lodash-es/isPlainObject';

esbuild output

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));

var import_isPlainObject = __toESM(require("lodash-es/isPlainObject"), 1);

if (!(0, import_isPlainObject.default)(methods)) {
  return typedJestCreateSpyObj(objType, methods);
}
@hyrious
Copy link

hyrious commented Aug 17, 2022

It looks like you're bundling your ESM code into CommonJS (or IIFE), but with "lodash-es" being externalized. (i.e. esbuild index.mjs --bundle --format=cjs --external:lodash-es) There are a few problems:

  1. In this mode, esbuild will convert all import to require calls to external modules.
  2. Importing an ESM-only module with require() is not allowed in any environment.
  3. Your source code ends with .mjs, which indicates esbuild to generate { default: module }, you can find more details in issues you've mentioned above.

The simplest workaround is do not externalize lodash-es, just remove it from your extenral settings.

@CSchulz
Copy link
Author

CSchulz commented Aug 17, 2022

I am not sure if I can match all your arguments with the call I have found in the jest preset:

 esbuild.transformSync(fileContent, {
    loader: 'js',
    format: 'cjs',
    target: 'es2016',
    sourcemap: compilerOpts.sourceMap,
    sourcefile: filePath,
    sourcesContent: true,
    sourceRoot: compilerOpts.sourceRoot,
});

https://github.com/thymikee/jest-preset-angular/blob/1ece7674231f5c422df4d2cae12ce3920a7346b9/src/ng-jest-transformer.ts#L61-L69

@hyrious
Copy link

hyrious commented Aug 17, 2022

format: 'cjs',

That's the cause. While as I know jest cannot run in ESM mode, so you may not be able to use lodash-es with jest.

@CSchulz
Copy link
Author

CSchulz commented Aug 19, 2022

We are using it in conjuction with jest-resolve. I was expecting jest-resolve will detect the correct order of the dependencies and will transform first lodash-es before trying to transform / load our library using lodash-es.
Is that not true?

@edisonLzy
Copy link

i have a question , why this callExpression second parameter is 1 in esbuild output result ? 🤔️

var import_isPlainObject = __toESM(require("lodash-es/isPlainObject"), 1);

@evanw
Copy link
Owner

evanw commented Sep 13, 2023

As you can see from the source code, the second parameter of __toESM is called isNodeMode. Node compatibility mode (including when and why it happens) is documented here: https://esbuild.github.io/content-types/#default-interop.

@edisonLzy
Copy link

As you can see from the source code, the second parameter of __toESM is called isNodeMode. Node compatibility mode (including when and why it happens) is documented here: https://esbuild.github.io/content-types/#default-interop.

i got a issue maybe related isNodeMode . here is a third dependence entry file in my project, it seem mark as ESM format already.

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(
    exports, "__esModule", { value: true });
const Graphemer_1 = __importDefault(require("./Graphemer"));
exports.default = Graphemer_1.default;

here is the output result, the isNodeMode has been marked as 1, this will cause __toESM unexpected working. check the comment in __toESM below.

var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // 🔧  it will hit this branch to define default props again, 
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));

var import_graphemer = __toESM(require_lib(), 1);
//  ❌  runtime error
var gaphemer = new import_graphemer.default();

@ApplY3D
Copy link

ApplY3D commented Nov 30, 2023

The same problem occurs when using import() with Angular 17 after building.
It's strange because I didn't encounter this problem while serving locally.

export const exportExcel = () => import('xlsx-js-style').then(xlsx => {})

Workaround:

const safeESModule = <T>(a: T | { default: T }): T => {
  const b = a as any;
  return b.__esModule || b[Symbol.toStringTag] === 'Module' ? b.default : b;
};

export const exportExcel = () => import('xlsx-js-style').then(safeESModule).then(xlsx => {})

@brookback
Copy link

I have this problem when bundling code for the browser and having type: module set in package.json.

  • Our repo contains both server side Node code and client code.
  • We bundle the client code with esbuild for the browser. On the server, we use native ESM on node 18.
  • Everything is in Typescript.
  • On the client, we have a dependency (xstream) which is built to CommonJS and uses exports.default.

I do:

import xs from 'xstream';

console.log(xs.never());

This fails when running in the browser with never() doesn't exist on undefined.

Removing type: module from package.json fixes this issue, but prevents us from doing ESM on the server.

The problem

  1. I want type: module for doing native ESM on the server in Node.
  2. esbuild notices this, and uses "node mode". This will set the second argument to truthy in __toESM:
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));

which in turn messes up the default import of xstream.

I've scanned the excellent docs and read the explanations in https://esbuild.github.io/content-types/#default-interop, but it feels like I'm between a rock and a hard place: I want ESM on the server, but don't want this to affect esbuild's bundling of client code.

Workaround

Using @ApplY3D's workaround above worked:

// safe-xstream.ts
import _xs from "xstream";

const safeESModule = <T,>(a: T | { default: T }): T => {
    const b = a as any;
    return b.__esModule || b[Symbol.toStringTag] === "Module" ? b.default : b;
};

const xs = safeESModule(_xs);

export default xs;
// index.ts
import xs from './safe-xstream';

console.log(xs.never());
Minimal repro
// src/index.ts
import xs from 'xstream';

console.log(xs.never());
// package.json
{
  "name": "tmp",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "./build-js.sh",
  },
  "dependencies": {
    "esbuild": "^0.20.1",
    "xstream": "^11"
  }
}
#!/bin/sh
# build-js.sh

set -eo pipefail

npx esbuild \
    --color=true \
    --bundle \
    --target=es2018 \
    --format=esm \
    --outfile=build/bundle-dev.js \
    ./src/index.ts
<!doctype html>
<html>
    <body>
        <script src="/bundle-dev.js" type="module"></script>
    </body>
</html>

@fightZy
Copy link

fightZy commented Mar 3, 2025

I encountered a similar dilemma where the project was running under ESM, so the build result isNodeMode was set to 1, but he is a node compatible module already marked with __esModule, then he was added an extra default, which led to the error

Image Image

Should we prioritize whether the module is already compatible with ESM?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants