rescript-association / genType

Auto generation of idiomatic bindings between Reason and JavaScript: either vanilla or typed with TypeScript/FlowType.

Home Page:https://rescript-lang.org/docs/gentype/latest/introduction

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Why force require() usage in typescript langage

mathieutu opened this issue · comments

Hi,
When in typescript language, the generate file uses const fooBS = require('./foo.bs'); instead of import * as fooBS from'./foo.bs'.

This is because of this switch:

https://github.com/reason-association/genType/blob/625106ff39720a3c787f6a31e1fe802e36d5a722/src/EmitType.re#L676-L693

With that configuration, I have a Uncaught ReferenceError: require is not defined error at runtime (using es6 modules directly in browser, with vitejs).

Why did you make this choise, instead of using standard imports, wich are the standard way of doing in typescript?

Thanks.
Mathieu

pinging @cristianoc on that, as he worked on this topic.

Edit: I've found this comment: #67 (comment)

@mathieutu the use of @genType.import is indeed what triggers this.
No easy solution, as the .bs.js uses .gen.tsx and .gen.tsx uses .bs.js.
One thing that comes to mind is: if the import requires no conversion, so no conversion code is generated in the .gen.tsx, then there's in principle no need for the .bs.js to use .gen.tsx. I fear forbidding types requiring conversion in imports would be too restrictive, though that would be a big simplification.
One could also consider e.g. @genType.check that only performs type checks, and fails or something if conversion is required.

Hey @cristianoc ! I read your past comments, as well as this one, and I still don't quite understand. Is there something deeper than the issue that importing individual files in TS will error because of the lack of types (for which you've found a pretty cool trick I think here #67 (comment)).
I'm asking because I'd really love to use snowpack for my development, but I can't seem to make it accept the files generated by gentype, as they have require in them and I haven't yet figured out how to transform the files to embed the require runtime.

Could you give us an example of .bs.js using .gen.tsx?

Files .bs.js use .gen.tsx whenever there is a @genType.import annotation on a value. (The implicit annotation @bs.module "Filename.gen" is inserted automatically by the compiler).
E.g. in https://github.com/reason-association/genType/blob/master/examples/typescript-react-example/src/ImportJsValue.bs.js#L3.

The reason being the imported function might require conversion, defined in the .gen.tsx file.
So the result is .bs.js and .gen.tsx use each other. The only trick I found to make this work is to use a require in the middle of the generated file:
https://github.com/reason-association/genType/blob/master/examples/typescript-react-example/src/ImportJsValue.gen.tsx#L86

So the problem only exists when @genType.import is used, and I don't know of a solution to this.
One direction for exploration I was mentioning above, is to introduce a new annotation e.g. @genType.check which acts like @genType.import and only works when a conversion is not required.

Why is it also the case for components?

@wokalski I'm probably missing something. Why do you think components would be different from other values in this respect?

Btw looking at this issue again, some thought came to mind, which could be worth exploring. What if, whenever a value is imported, we forbid any (typed) export from that file. That would be some kind of unidirectional import/export restriction. So when importing a value, the .bs.js would need to access the .gen file, but not the other way round.

If I'm reading the code correctly (and certainly looking at the generated code - I don't have any genType.imports), using gentype on any component such as:

[@genType.as "GlobalHost"]
let globalHost = GlobalHost.make;

causes a require to appear in the genType'ed code.

Another thought that came to my mind is what if we removed this circular dependency altogether? What if genType.import didn't depend on .gen and rather emitted appropriate externals?

I wanted to change this but it looks like it's handled internally by rescript compiler now:
https://github.com/rescript-association/genType/blob/eab8055ef7d3d7e2172f4b9f370ecef4e8e36b19/Changes.md#2170

I don't fully understand this but it seems to me like this issue could be avoided altogether if Bucklescript files imported from the referenced modules directly rather than going through .gen. When is it useful to go TS -> .gen -> .bs.js rather than Ts -> .gen and .bs.js -> .gen directly?

When genType needs to perform a conversion (because the runtime representation is not the same), that needs to happen in .gen. And .bs.js uses it.

Summarizing the issue again. GenType imports need importing in one direction. GenType exports need importing in the other direction. By carefully ordering things, and using a require, one can support these circular references. (I haven't found another way, but I'm no way an expert in that).

So one possible alternative solution is to prevent circular references. E.g. by forbidding genType exports when genType imports are used.

I think there's also bug as I noted above.

[@genType.as "GlobalHost"]
let globalHost = GlobalHost.make;

This (where GlobalHost is a React component module) causes a require to appear. Also, Curry seems to be required, too. Could you confirm it's a bug indeed or another misunderstanding on my part?

I have confirmed I have 0 occurrences of genType.import in my codebase yet requires appear in all places - notably all .bs imports and Curry imports are performed using require.

My bsconfig.json:

{
  "name": "web",
  "version": "0.1.0",
  "reason": {
    "react-jsx": 3
  },
  "sources": [
    {
      "dir": "src",
      "subdirs": true
    }
  ],
  "package-specs": {
    "module": "es6",
    "in-source": true
  },
  "suffix": ".bs.js",
  "bs-dependencies": [
    "@ahrefs/bs-atdgen-codec-runtime",
    "@ahrefs/bs-emotion",
    "bs-fetch",
    "bs-webapi",
    "dialomorphic",
    "reason-react",
    "reason-relay",
    "reason-promise",
    "re-classnames",
    "shared"
  ],
  "pinned-dependencies": [
    "dialomorphic",
    "shared"
  ],
  "ppx-flags": [
    "reason-relay/ppx",
    "@davesnx/styled-ppx/styled-ppx"
  ],
  "warnings": {
    "number": "-44",
    "error": "+101"
  },
  "refmt": 3,
  "gentypeconfig": {
    "module": "es6",
    "language": "typescript",
    "shims": {
      "ReasonRelay": "ReasonRelayShim"
    },
    "debug": {
      "all": false,
      "basic": false
    }
  }
}

More discussion in #520

Update: after @wokalski's changes in #520, there is only the case of circular dependencies, using require. And these can be avoided with a simple reccommendation:

  • If a file uses @genType.import for a value, avoid exporting things wiht @genType in the same file.

By doing the above, no require's will be generated.

Really happy to finally be able to close this issue, and have a simple recommendation to avoid require's and circular dependencies in all back-ends.

Thanks @wokalski