If a module’s default export is an object containing functions, then you probably need to write a record or object type and use that in your binding, like this:
type t = {
zonedTimeToUtc: (Js.Date.t, string) => Js.Date.t,
}
@module("date-fns-tz") external dateFnsTz: t = "default"
Although I’m wondering if you actually should use the default export in this scenario. I’m not familiar with date-fns-tz, but, from glancing at their README, it has this example code:
import { zonedTimeToUtc } from 'date-fns-tz'
That’s a named export, not a default export. In that case, import * as DateFnsTz from "date-fns-tz"; would be correct and import DateFnsTz from "date-fns-tz"; would be incorrect. Are you getting an error from not using the default?
Edit: Something else I just realized is that date-fns-tz has both an es6 module and a commonjs module. If you’re importing it from your own es6 module, then you may be inadvertently using the commonjs version, not the es6. IIRC, Node will let you import commonjs into a module but it puts all of their exports into one default export. From glancing at the repo, you may be able to directly import the es6 module version with the path date-fns-tz/esm
I hit this issue in an effort to switch from CommonJS to ES6 modules. My jest tests immediately started to fail with my current import * as ... and when I manually changed the generated Javascript to Import DateFnsTz it worked.
TypeError: DateFnsTz.utcToZonedTime is not a function
9 | if (date !== undefined) {
10 | return Caml_option.some(
> 11 | DateFnsTz.utcToZonedTime(Caml_option.valFromOption(date), timeZone)
| ^
That’s a good find about the ES6. The fix sadly doesn’t work for me, because then Node doesn’t recognize the ES6 module as ES6 because it ends in .js, not .mjs:
/Users/dfalling/Code/ido/node_modules/date-fns-tz/esm/index.js:3
export { default as format } from './format/index.js'
^^^^^^
SyntaxError: Unexpected token 'export'
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1350:14)
Node’s handling of ES6 modules is such a nightmare. I’ve sunk easily 6 hours into trying to trick it into working.
Yeah, that’s the unfortunate state of ES6 modules in Node. For a long time, there wasn’t any official ES6 module support, so the entire ES6 module ecosystem was built around using transpilers like Babel to convert ES6 modules into commonjs modules. Now that Node has its own ES6 module system, the packages based around using transpilers aren’t necessarily compatible with it. date-fns-tz seems to be in that category.
If you’re committed to using ES6 modules, then the most frictionless way of handling this is probably using a transpiler instead of Node’s built-in ES6 module system. However, I don’t claim to be an expert, so maybe there’s a better solution I’m not aware of.
However doing so turns off the magic ES6 default export handling and produces DateFnsTz.zonedTimeToUtc.default(). The only way to do it right now is with an intermediate type; I’ve been meaning to log a request to improve this, because it’s quite awkward.
should compile to the code you’re looking for (sort of). In this style I recommend hiding the externals with an interface to avoid confusion.
I agree. The recent changes to ReScript to support ES6 projects helped a lot but there are many weird bits. I came close to migrating, but in the end I still have to compile as both CJS and ES6 because my CI server is too old to run NodeJS 15.
Btw one thing that might help is that modern node does some magic so that things like import express from "express" actually works, when if you think about it, it shouldn’t.
The module import/export thing in JS has been so messy; it’s not great. One solution is to write a tiny JS module to import and re-export into the format you’d like ReScript to consume, but we caution against that approach too since there are lots of weird transpiler-specific problems even with this solution. One day it’ll get better…
Node has its own implementation of es6 modules. I had some headaches about it without using ReScript. This behavior also changed during upgrade from Node 12 (experimental) to Node 14.
TypeScript also has issues regarding this. I want to research this further, but be sure to use at least Node 14, with “type”: “module” set on package.json. You shouldn’t have to rename files to “cjs”.
Also, the import generated by bs doesn’t work well for me: import * as x from 'x';
should be in some cases: import x from 'x';
This also changes whether the imported module is CommonJs or ES6. In some cases I couldn’t destructure in javascript CommonJs modules on import.
Just as an example, this is how I am importing Chalk (for it to work properly):
@module("chalk")
external chalk: chalk = "default"
@send external magenta_: (chalk, string) => t = "magenta"
let magenta = text => chalk->magenta_(text)
@send external red_: (chalk, string) => t = "red"
let red = text => chalk->red_(text)
@send external white_: (chalk, string) => t = "white"
let white = text => chalk->white_(text)
@send external yellow_: (chalk, string) => t = "yellow"
let yellow = text => chalk->yellow_(text)
Is this the way we do it right now to support ES6 imports?