Description
Background
ESM Import Attributes has reached stage 3 (Import Assertion is deprecated, so it will never get standardized)
This is important because it can be massively used by various tools around the JavaScript ecosystem and even native ESM modules.
Previously, if we wanted to modify module semantics, we had to customize the complex configuration of the tool like bundlers. But now it can be done at module level.
For example, in the import statement for an image asset we could do this:
import imageDataUrl from './image.png' with { loader: 'url-loader' }
import imageUrl from './image.png' with { loader: 'file-loader' }
Another example is importing WebAssembly assets:
const wasmInstance = await import("foo.wasm", { type: "module", with: { type: "webassembly" } });
// or directly create a module worker
new Worker("foo.wasm", { type: "module", with: { type: "webassembly" } });
ReScript @module
FFI currently lacks the resolution for this.
Detailed Design
This proposes a way to express import attributes by extending the existing FFI syntax.
@module
syntax
If import attribute is needed, add { with: { ... } }
argument to the @module
directive.
The argument payload should be validated as a record type
type modulePayload<'a> = {
from?: string,
with?: ({..} as 'a),
}
// ModuleBindings.res
@module({ from: "foo.wasm", with: { "type": "webassembly" } }) @val
external wasmInstance: WebAssembly.Instance.t = "default"
@module({ from: "schema.json", with: { "type": "json" } }) @val
external jsonSchema: Json.t = "default"
// allow using it on non-default target
@module({ with: { "my-custom-loader": "i18n" } }) @val
external messages: Messages.t = "i18n.js"
should be compiled to:
import WasmInstance from "foo.wasm" with { type: "webassembly" };
import SchemaJson from "schema.json" with { type: "json" };
import * as Messages from "i18n.js" with { "my-custom-loader": "i18n" };
var wasmInstance = Instance;
var jsonSchema = SchemaJson;
var messages = Messages;
export {
wasmInstance,
jsonSchema,
messages,
};
Dynamic imports
Dynamic imports include full module import (module mod = await MyModule
) and partial import syntax (Js.import(MyModule.fn)
).
Because ReScript modules are always JavaScript modules, the dynamic imports syntax doesn't need import attributes.
Note on dynamic imports of external modules
await Js.import(ModuleBindings.wasmInstance);
will be compiled to
await import("./ModuleBindings.bs.js").then(function (m) {
return m.wasmInstance;
});
Which is legit.
However, its behavior isn't semantically equivalent to await import("foo.wasm", { type: "module", with: { type: "webassembly" } })
. The ReScript version always requires additional loading of JavaScript modules, which adds unnecessary waterfall.
Maybe we need to reconsider its design or extra syntax to directly import assets.
CommonJS
There is no equivalent semantics in CommonJS. Users can still use the syntax, but the output will not change.
Exports
There are no specific use cases for exports attributes. Since ReScript's expression and output are always about JavaScript.
Maybe useful some integration cases