esbuild
esbuild copied to clipboard
ESM import in ESM modules results into TypeError: (0 , import_....default) is not a function
I have issues with ESM imports in ESM modules. I am using [email protected] but also [email protected] 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);
}
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:
- In this mode, esbuild will convert all
importtorequirecalls to external modules. - Importing an ESM-only module with
require()is not allowed in any environment. - 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.
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
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.
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?
i have a question , why this callExpression second parameter is 1 in esbuild output result ? 🤔️
var import_isPlainObject = __toESM(require("lodash-es/isPlainObject"), 1);
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.
As you can see from the source code, the second parameter of
__toESMis calledisNodeMode. 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();
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 => {})
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 usesexports.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
- I want
type: modulefor doing native ESM on the server in Node. - 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>