bundles .mjs code when .cjs (commonjs) is expected
I'm new to esbuild, so I'm not sure if this is intended, but I'm at least confused 😄
I have the following
// package.json
{
"scripts": {
"build": "esbuild main.cts --bundle --platform=node --format=cjs --outfile=dist/out.js"
},
"dependencies": {
"esbuild": "^0.19.2",
"locter": "^1.2.2"
}
}
// main.cts
import { isFilePath } from "locter";
isFilePath("foo")
I'm expecting dist/out.js to be commonjs and bundled commonjs code from locter.
Which have package.json exports defined as follows:
// loctor's package.json exports
"exports": {
"./package.json": "./package.json",
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
}
},
However dist/out.js have this
// node_modules/locter/dist/index.mjs
var import_jiti = __toESM(require_lib(), 1);
var import_url3 = __toESM(require("url"), 1);
var import_path2 = __toESM(require("path"), 1);
var import_module = __toESM(require("module"), 1);
var import_meta = {};
var __filename = import_url3.default.fileURLToPath(import_meta.url);
var __dirname = import_path2.default.dirname(__filename);
var require2 = import_module.default.createRequire(import_meta.url);
function isFilePath(input) {
const extension = import_node_path.default.extname(input);
return extension && extension !== "";
}
I was expecting // node_modules/locter/dist/index.cjs and the code from that file.
The .mjs code contains import.meta.url which breaks my code runtime. But that's besides the point.
Am I doing something wrong here?
Using an import statement matches the import condition, not the require condition. If you want to match the require condition instead then you should use require instead of import:
const { isFilePath } = require("locter");
I thought that this typescript
import { isFilePath } from "locter";
was turned into
const { isFilePath } = require("locter");
if the compile target is commonjs
From TS docs https://www.typescriptlang.org/tsconfig/#module
// @filename: index.ts import { valueOfPi } from "./constants"; export const twoPi = valueOfPi * 2;CommonJS
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.twoPi = void 0; const constants_1 = require("./constants"); exports.twoPi = constants_1.valueOfPi * 2;ESNext
import { valueOfPi } from "./constants"; export const twoPi = valueOfPi * 2;
I agree, you shouldn't need to use require() in typescript files. The file extension of .cts should be enough to indicate that the target is commonjs, and therefore the require condition should be used.
@evanw would love your input on this.
Is some kind of preprocessing supposed to be done on TypeScript files before it's ready for esbuild?
Reading from https://esbuild.github.io/content-types/#es-module-interop
import * as foo from 'foo'is compiled toconst foo = require('foo')
Given that esModuleInterop: false
However shouldn't esModuleInterop: true and "module": "commonjs" (tsconfig.json) also do
import * as foo from 'foo'is compiled toconst foo = require('foo')
I did some more testing and I can't get esbuild to convert import to require no matter what I set in tsconfig.
I must be missing something here.
From my personal perspective, I do not want esbuild to convert ts import to js require by tsconfig.
-
It is only a historical behavior. Now the whole js community is moving forward to ESM. It is very strange to suddenly step back and give users CJS when they are writing code using
import. -
Complexity. The bundler now has to consider these configs / filenames, which is hard to figure out by a normal developer:
- package.json > type: module
- filename ends with .mjs .cjs (mts cts)
- file's relative tsconfig.json
- module: commonjs
- target: es6 (which indicates module: es6)
- but esbuild actually cannot handle target: es5 when the code uses es6 syntax, which is the default config, should it throw many errors to stop you from bundling?
- files / includes
- esModuleInterop indicates different wrappers to handle external modules, however it should always be configured to true for modern projects
-
By using require you're preventing esbuild's tree shaking from taking effect.
I'm closing this as it doesn't seem to be going anywhere.
I must be misunderstanding something. I mean a lot of projects use esbuild and the internet isn't on fire 😃