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

".mts" and ".cts" default format #4

Closed
PabloSzx opened this issue Oct 8, 2021 · 5 comments
Closed

".mts" and ".cts" default format #4

PabloSzx opened this issue Oct 8, 2021 · 5 comments

Comments

@PabloSzx
Copy link

PabloSzx commented Oct 8, 2021

Shouldn't the config have these defaults to follow the TypeScript logic?

{
  ".mts": {
    "format": "esm"
  },
  ".cts": {
    "format":  "cjs"
  }
}

One could also argue that these extensions format shouldn't even be configurable and force these formats, just as .cjs forces commonjs and .mjs forces ecmascript modules

@lukeed
Copy link
Owner

lukeed commented Oct 8, 2021

It's just invalid TS to have require in a mts file and static import in a cts file. So, like your last paragraph, there's no reason to configure it imo.

Furthered by the fact that these extensions exist so that your typescript can include

import { foobar } from "./hello.mjs";
// Automatically resolved to hello.mts

@PabloSzx
Copy link
Author

PabloSzx commented Oct 8, 2021

It's just invalid TS to have require in a mts file and static import in a cts file. So, like your last paragraph, there's no reason to configure it imo.

Furthered by the fact that these extensions exist so that your typescript can include

import { foobar } from "./hello.mjs";
// Automatically resolved to hello.mts

nothing you said changes the fact that tsm should have "format": "cjs" for ".cts" and "format": "esm" for ".mts", which is what I mentioned

@lukeed
Copy link
Owner

lukeed commented Oct 8, 2021

Actually it does, because the integrity of the file itself is determined by the TS checker. tsm transforms the files based on how it was used, so -r loads items as CommonJS and --loader/cli loads it as ESM. So long as the semantics of the files' contents are preserved (they are), then interchanging formats is fine so long as it's consistent.

This is/was a big reason why others like ts-node and even esm are still high friction, because they provide a level of interop up until they don't and then you're stuck in a weird spot.

A real-world example of this is the following:

// src/math.mjs
// or src/math.js w/ type: module
export const sum = (a, b) => a + b;

// test/math.ts
import * as assert from 'assert';
import * as math from '../src/math';

assert.equal(math.sum(1, 2), 3);

run via

$ node -r tsm test/math.ts

This would convert the TS file into require statements, only to load an ESM file that wasn't transformed. Error. Instead, allow the tool to safely translate the contents into their equivalents, as if it were all passing thru a bundler anyway.

The above would work with node --loader tsm test/math.ts or tsm test/math.ts but only because tsm is loaded thru ESM usage and converts the (unaffiliated) TS into ESM, which then handles the source ESM natively. However, working 2/3 times is just added friction when there's no reason to be.

Forcing a format here breaks this and really has no benefit.

@lukeed lukeed closed this as completed Oct 8, 2021
@PabloSzx
Copy link
Author

PabloSzx commented Oct 8, 2021

your example doesn't have anything to do with what I mentioned, what I am suggesting is following the Typescript 4.5 new convention of .mts is always ESM and .cts is always CJS, that's it, following this convention allows you to specify what is the expected format of the transpilation, since I will know that a typescript file with .cts will have "require" available, while ".mts" will always be esm, that's it

@lukeed
Copy link
Owner

lukeed commented Oct 8, 2021

I understand you, but I don't think you're understanding me.

Forcing a format would break this example. That's because when the src/math.mjs – or src/math.mts for that matter – is loaded, your suggestion would always make it result in ESM syntax (because, natively, it should/is). However, if that were to happen, then the node -r tsm test/math.ts case would have code that looks like this:

// src/math.mjs (converted, forced/remains as ESM)
export const sum = (a, b) => a + b;

// test/math.ts (converted, forced as CJS because of --require hook)
const assert = require('assert');
const math = require('../src/math.mjs');
// ^^ THIS IS STILL ESM -> throws syntax error

Instead, for tsm transpilation, we need to ignore the file's required format and transform it to the usage's desired format. This can only work if the converter itself produces semantically correct format conversion ... and esbuild does.

So instead, when you run node -r tsm test/math.ts on the above example, you should get this:

// src/math.mjs (converted, forced because of --require hook)
const sum = (a, b) => a + b;
exports.sum = sum;

// test/math.ts (converted, forced because of --require hook)
const assert = require('assert');
const math = require('../src/math.mjs');
// ^^ THIS IS NOW COMMONJS -> success

The semantics are preserved. And the same guarantee happens when you run tsm directly or use --loader tsm except everything is coerced into ESM syntax instead.

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

2 participants