Skip to content

Latest commit

 

History

History
244 lines (178 loc) · 8.18 KB

import-from-export-to-js.mdx

File metadata and controls

244 lines (178 loc) · 8.18 KB
title description canonical
Import from / Export to JS
Importing / exporting JS module content in ReScript
/docs/manual/v11.0.0/import-from-export-to-js

Import from/Export to JS

You've seen how ReScript's idiomatic Import & Export works. This section describes how we work with importing stuff from JavaScript and exporting stuff for JavaScript consumption.

If you're looking for react-specific interop guidance, check out the React JS Interop guide.

Note: due to JS ecosystem's module compatibility issues, our advice of keeping your ReScript file's compiled JS output open in a tab applies here more than ever, as you don't want to subtly output the wrong JS module import/export code, on top of having to deal with Babel/Webpack/Jest/Node's CommonJS <-> JavaScript module compatibility shims.

In short: make sure your bindings below output what you'd have manually written in JS.

Output Format

We support 2 JavaScript import/export formats:

  • JavaScript module: import * from 'MyReScriptFile' and export let ....
  • CommonJS: require('myFile') and module.exports = ....

The format is configurable in via rescript.json.

Import From JavaScript

Import a JavaScript Module's Named Export

Use the module external:

<CodeTab labels={["ReScript", "JS Output (Module)", "JS Output (CommonJS)"]}>

// Import nodejs' path.dirname
@module("path") external dirname: string => string = "dirname"
let root = dirname("/User/github") // returns "User"
import * as Path from "path";
var root = Path.dirname("/User/github");
var Path = require("path");
var root = Path.dirname("/User/github");

Here's what the external does:

  • @module("path"): pass the name of the JS module; in this case, "path". The string can be anything: "./src/myJsFile", "@myNpmNamespace/myLib", etc.
  • external: the general keyword for declaring a value that exists on the JS side.
  • dirname: the binding name you'll use on the ReScript side.
  • string => string: the type signature of dirname. Mandatory for externals.
  • = "dirname": the name of the variable inside the path JS module. There's repetition in writing the first and second dirname, because sometime the binding name you want to use on the ReScript side is different than the variable name the JS module exported.

Import a JavaScript Module As a Single Value

By omitting the string argument to module, you bind to the whole JS module:

<CodeTab labels={["ReScript", "JS Output (Module)", "JS Output (CommonJS)"]}>

@module external leftPad: (string, int) => string = "./leftPad"
let paddedResult = leftPad("hi", 5)
import * as LeftPad from "./leftPad";
var paddedResult = LeftPad("hi", 5);
var LeftPad = require("./leftPad");
var paddedResult = LeftPad("hi", 5);

Depending on whether you're compiling ReScript to JavaScript module or CommonJS, this feature will generate subtly different code. Please check both output tabs to see the difference. The JavaScript module output here would be wrong!

Import an default Export

Use the value default on the right hand side:

<CodeTab labels={["ReScript", "JS Output (Module)"]}>

@module("./student") external studentName: string = "default"
Console.log(studentName)
import Student from "./student";
var studentName = Student;

Use Import Attributes

Since 11.1

Import attributes can be used in ReScript, as long as ReScript is configured to output JavaScript module. You do that by passing configuration to the @module attribute:

<CodeTab labels={["ReScript", "JS Output (Module)"]}>

@module({from: "./myJson.json", with: {type_: "json", \"some-exotic-identifier": "someValue"}})
external myJson: JSON.t = "default"

Console.log(myJson)
import MyJsonJson from "./myJson.json" with {"type": "json", "some-exotic-identifier": "someValue"};

var myJson = MyJsonJson;

console.log(myJson);

This above imports the local ./myJson.json file, adding import attributes.

This is how it works:

  1. Instead of passing a string or tuple to @module, pass a record.
  2. This record should have a from key. The value of that is where you want the module to be imported from (just like the regular string to @module is).
  3. It should also have a with key, with another record where you put all the import attributes you want emitted.

Notice \"some-exotic-identifier" - you'll need to escape any key that's not a valid ReScript record key. Also notice type_. Since type is a reserved keyword in ReScript, you can use type_ instead. It will be output as type in the JavaScript code.

Dynamic Import

Leveraging JavaScript's dynamic import to reduce bundle size and lazy load code as needed is easy in ReScript. It's also a little bit more convenient than in regular JavaScript because you don't need to keep track of file paths manually with ReScript's module system.

Dynamically Importing Parts of a Module

Use the import function to dynamically import a specific part of a module. Put whatever let binding you want to import in there, and you'll get a promise back resolving to that specific binding.

Let's look at an example. Imagine the following file MathUtils.res:

let add = (a, b) => a + b
let sub = (a, b) => a - b

Now let's dynamically import the add function in another module, e.g. App.res:

<CodeTab labels={["ReScript", "JS Output (Module)"]}>

// App.res
let main = async () => {
  let add = await import(MathUtils.add)
  let onePlusOne = add(1, 1)

  Console.log(onePlusOne)
}
async function main() {
  var add = await import("./MathUtils.mjs").then(function(m) {
    return m.add;
  });

  var onePlusOne = add(1, 1);
  console.log(onePlusOne);
}

Dynamically Importing an Entire Module

The syntax for importing a whole module looks a little different, since we are operating on the module syntax level; instead of using import, you may simply await the module itself: <CodeTab labels={["ReScript", "JS Output (Module)"]}>

// App.res
let main = async () => {
  module Utils = await MathUtils

  let twoPlusTwo = Utils.add(2, 2)
  Console.log(twoPlusTwo)
}
async function main() {
  var Utils = await import("./MathUtils.mjs");

  var twoPlusTwo = Utils.add(2, 2);
  console.log(twoPlusTwo);
}

Export To JavaScript

Export a Named Value

As mentioned in ReScript's idiomatic Import & Export, every let binding and module is exported by default to other ReScript modules (unless you use a .resi interface file). If you open up the compiled JS file, you'll see that these values can also directly be used by a JavaScript file too.

Export a default Value

If your JS project uses JavaScript module, you're likely exporting & importing some default values:

// student.js
export default name = "Al";
// teacher.js
import studentName from 'student.js';

A JavaScript default export is really just syntax sugar for a named export implicitly called default (now you know!). So to export a default value from ReScript, you can just do:

<CodeTab labels={["ReScript", "JS Output (Module)", "JS Output (CommonJS)"]}>

// ReScriptStudent.res
let default = "Bob"
var $$default = "Bob";

exports.$$default = $$default;
exports.default = $$default;
// informal transpiler-compatible marker of a default export compiled from JavaScript module
exports.__esModule = true;
var $$default = "Bob";

export {
  $$default,
  $$default as default,
}

You can then import this default export as usual on the JS side:

// teacher2.js
import studentName from 'ReScriptStudent.js';

If your JavaScript's default import is transpiled by Babel/Webpack/Jest into CommonJS requires, we've taken care of that too! See the CommonJS output tab for __esModule.