question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

TypeScript module not follow specification in some cases when compilerOptions.module = commonjs

See original GitHub issue

TypeScript Version: 3.9.7

When, in a index file, it is exported the content of another file, and tsconfig compilerOptions.module = commonJs, tsc transpiles it with read-only accessors, which is not complying with the behavior described in the official documentation

Search Terms: exports accessor, export readonly, export accessor readonly, export getter, compilerOptions module

Code tsconfig file is configured for commonJs module output

{
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src",
    "target": "es2018",
    "module": "commonjs",
    "strict": true
  }
}

File my-test.ts

export function myTest() {
  return 1;
}

File index.ts

export * from "./my-test";

export function myTest2() {
  return 3;
}

Supposing it is transpiled to a dist folder, you can have a javascript file with this code:

const test = require('./dist');
test.myTest = function () {
  return 2;
}

console.log(test.myTest());

Alternative:

import test = require("./index");
Object.defineProperty(test, "myTest", {
  get() {
    return 2;
  },
});

console.log(test.myTest());

This substitution is not production material, but a simulation of mocking/stubbing on unit tests. Libraries like sinon do something like this to achieve the mocking. Notice that, in a real project, I’m using typescript register + mocha, not running directly the compiled code, but with this setup is easier to simulate the problem.

Expected behavior: I expect that the js code prints 2;

Actual behavior: In js alternative 1, It prints 1, in js alternative 2, it throws an error; The problem happens because of how the index.js is generated. Since 3.9.2, it is generated like this:

 
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.myTest2 = void 0;
__exportStar(require("./my-test"), exports);
function myTest2() {
    return 3;
}
exports.myTest2 = myTest2;

Notice that, in the line 4 of this code, an accessor is defined. On version 3.8.3, it generates like this:

"use strict";
function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./my-test"));
function myTest2() {
    return 3;
}
exports.myTest2 = myTest2;

Notice that the function declared in the index.ts was exported correctly, following what is specified in the official documentation, but both generated codes actually are not in total compliance with it. The problem is that, with the latest version, it also changes the behavior, making read-only the noncompliant exporting, which can break code that relies on it being read-write, like mocking mechanism in unit tests, for example. The unit tests could also have a distinct tsconfig using modules = commonJs just to have such behavior allowing mocking, while the main tsconfig not.

Playground Link: I’m unable to create a playground example as this issue only happens using multiple files, but i created a example project: https://github.com/Farenheith/issue-readonly-exporting-example

Related Issues: this issue but it was marked as work as intended, probably because I didn’t notice that the transpiler was not following the compiler option module configuration the first time I opened it.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:13 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
Farenheithcommented, Jan 8, 2021

Same problem. I have to back to v3.8.3 to make sinon working, is there any progress in the follow-up?

@FeelyChau I found a way to make the tsc generated commonjs modules mockable again. It’s a workaround but works with version 4.1.3 of typescript! I created a package to make it easy to use: https://www.npmjs.com/package/mockable-ts-commonjs

1reaction
Farenheithcommented, Sep 22, 2020

@FeelyChau maybe using some rewire strategy https://stackoverflow.com/questions/38273619/using-sinonjs-stub-with-rewire is the best way to go. I don’t know if some mocking library have it already implemented, I think sinon does not

Read more comments on GitHub >

github_iconTop Results From Across the Web

TSConfig Reference - Docs on every TSConfig option
Modules. allowUmdGlobalAccess,; baseUrl,; module,; moduleResolution, ... In some cases where no type annotations are present, TypeScript will fall back to a ...
Read more >
What is module option in tsconfig used for? - Stack Overflow
json, this means that the modules in the compiled .js files will use the commonJS (CJS) syntax, so var x = require(...) and...
Read more >
How To Use Modules in TypeScript | DigitalOcean
Modules are a way to organize your code into smaller, more manageable pieces. TypeScript evolved along with ECMAScript specifications to ...
Read more >
API - esbuild
Entry points with exports in CommonJS module syntax will be converted to a single ... While esbuild is not the optimal JavaScript minifier...
Read more >
CommonJS modules | Node.js v19.3.0 Documentation
By default, Node.js will treat the following as CommonJS modules: ... When the entry point is not a CommonJS module, require.main is undefined...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found