2

Background

I have a file that I need to share between the two repositories. The file contains one single object.

  • Repo A is set to only accept commonjs files (require("/path/to/file")) with no easy access for me to its babel configs.
  • Repo B is using ES6 import foo from "bar" import style, and I can play with its tsconfig.json.

I know that if I use modules.export = myObject it only works for the commonjs repo (repo A), and if I do export = myObject or export default myObject it only works for the ES6 repo (repo B).

Questions:

  1. Is this possible to make the same file work for both systems? I tried export = modules.export = myObject or any other combination but none works.

  2. How can I import the commonjs version in ES6 repo? I tried leaving it a commonjs modules.export = myObject file, but in Repo B when I do import foo from "path/to/file" it keeps crying the following message. it goes away the moment I change it to export = myObject, but then it wouldn't work for Repo A.

File '/path/to/file.ts' is not a module.ts(2306)

Notes

  1. I found that setting "esModuleInterop": true in my tsconfig.json should make it work, but no matter what I do, (any "module" value in tsconfig.json, turning on allowSyntheticDefaultImports) it doesn't work. I keep getting the same 2306 error.
  2. As I mentioned in the comment, it can be considered that the file contains a static object. (e.g. {a: 5, b: 8};)

tsconfig.json

The part in repo A that I am touching is the config file for Quasar and it's in vanilla JS.

Repo B is Typescript/Node project, and the relevant part of the tsconfig.json in there is:

"compilerOptions": {
        "baseUrl": ".",
        "esModuleInterop": true,
        "experimentalDecorators": true,
        "module": "commonjs",
        "moduleResolution": "node",
        "outDir": "dist",
        "strict": true,
        "sourceMap": true,
        "target": "es6",
        ...
Aidin
  • 25,146
  • 8
  • 76
  • 67

3 Answers3

2

I just found that as long as the file is a .json file with proper JSON format, one can use it both in ES6 (import) and CommonJS (require).

Per this answer (and this article), for ES6 you need to enable "resolveJsonModule": true, in tsconfig.json and then you can have the content in both formats.

So if the file content.json has the following content (with no comment or anything else):

{
   "age": 34
}

then we will have ...

import * as jsonContent from "../../content.json"

console.log(jsonContent.age); // prints out 34

as well as

const jsonContent = require("../../content.json");

console.log(jsonContent.age); // prints out 34

They both print out the content successfully!

Aidin
  • 25,146
  • 8
  • 76
  • 67
0

I have found a solution, however the import syntax in commonjs land is not the nicest, but I hope it's usable for you.

You've provided no information to how your projects are setup other than specifying commonjs and es6 style imports so I'm going out on a limb here. Also worth noting that while including the files worked, when using commonjs-style imports they were typed as rather than retaining their typescript typings (I was not able to get typing using commonjs-style imports (require) - I haven't used this import syntax for a while so the correct tsconfig.json options to make it work escape me).


I moved the shared code into it's own project and compiled it with the following tsconfig settings:

Shared tsconfig.json:

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "ES5",
        "lib": [ "ES5" ],
    }
}

Shared.ts:

export default {'a': 1, 'b': 2};

Shared.js (output):

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = { 'a': 1, 'b': 2 };

(Note that this output is commonjs style, because ES6 is compatible with commonjs files).


When using require directly you need to reference the .default variable on the imported file:

commonjs.ts

var val = require("../shared/shared").default;

console.log(val);

However if you are configured so that you can write ES6-style imports and output to commonjs you can use the following syntax instead (this will provide you with typing information):

commonjs.ts

import val from "../shared/shared";

console.log(val);

Both of the above files were compiled with the following tsconfig:

tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "lib": [ "es5" ],
        "types": ["node"]
    }
}

The ES6 import is pretty straight-forward:

es6.ts

import shared from "../shared/shared";

console.log(shared);

tsconfig.json:

{
    "compilerOptions": {
        "module": "ES6",
        "target": "es6",
        "lib": [ "es6" ]
    }
}

I have a sneaking suspicion this doesn't 100% address your issue, but I'd need your tsconfig settings for each project as the version of the typescript compiler you're using for each project to make a more accurate answer.

Griffork
  • 683
  • 1
  • 5
  • 21
0

I exprimented a bit more and made another solution that you might prefer.

I still haven't figured out how to get typing on common-js style imports, but the import does work if run and doesn't cause compiler errors.


I moved the shared code into it's own project and made it compile to a different directory. This is because the "esModuleInterop" flag does not work if importing a typescript file, so instead I compiled the javascript file with a supporting .d.ts file to another directory so that the source .ts file doesn't cause a conflict.

Shared tsconfig.json:

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "ES5",
        "lib": [ "ES5" ],
        "declaration": true,
        "outDir": "../sharedOut"
    }
}

Shared.ts:

export = {'a': 1, 'b': 2};

And in the ouput folder you should get:

Shared.js:

"use strict";
module.exports = { 'a': 1, 'b': 2 };

Shared.d.ts

declare const _default: {
    a: number;
    b: number;
};
export = _default;

This causes you commonjs import to look like this:

commonjs.ts

var val = require("../sharedOut/shared");

console.log(val);

or (if you can use ES6-style imports but need the output to be commonjs):

import val from "../sharedOut/shared";

console.log(val);

Both of the above files were compiled with the following tsconfig:

tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "lib": [ "es5" ],
        "types": ["node"],
        "esModuleInterop": true
    }
}


The ES6 import is (again) pretty straight-forward:

es6.ts

import shared from "../shared/shared";

console.log(shared);

tsconfig.json:

{
    "compilerOptions": {
        "module": "ES6",
        "target": "es6",
        "lib": [ "es6" ],
        "esModuleInterop": true
    }
}


Again if this doesn't address your issue I'll need more information to progress. I'd need your tsconfig settings for each project as the version of the typescript compiler you're using for each project to make a more accurate answer.

Griffork
  • 683
  • 1
  • 5
  • 21
  • I made this answer based on the answer to this question: https://stackoverflow.com/questions/56238356/understanding-esmoduleinterop-in-tsconfig-file?rq=1 – Griffork Feb 10 '21 at 01:03
  • Thanks for the time you spent on this @griffork. Unfortunately, the `tsconfig.json` is not shared between the repos, and in fact one of them is in vanilla JS. I included the tsconfig.json on Repo B, in case you want to take a look. – Aidin Feb 11 '21 at 18:22
  • No problem! None of the tsconfigs are shared in my answers by the way, I supply the ts configs because you need that information to pick which solution will work for you. I believe you will need a separate tsconfig for the shared file and you will need to precompile the file before inclusion into both projects if using this solution. If you use the other answer of mine you will only need to precompile the file for the javascript project and change the require calls to reference the .default property. The ts project should be able to directly include the ts file... – Griffork Feb 12 '21 at 13:14
  • Both answers have straight javascript solutions for importing the shared file (just use the code snippets that say require). I was compiling them with ts but they are vanilla javascript and will work without the provided tsconfig. – Griffork Feb 12 '21 at 13:16
  • 1
    I awarded the bounty to you @griffork, to appreciate the time you spent on these answers. However, none of them solve my problem. I neither want to have "a separate tsconfig for the shared file", nor a "separate preconfig". Thanks again for your time. :) – Aidin Feb 16 '21 at 23:54