Rescript-compiler: Support dynamic import

Created on 21 Apr 2018  路  10Comments  路  Source: rescript-lang/rescript-compiler

_What version of OCaml are you using?_
This is using [email protected].

It would be nice if bucklescript supported code splitting via dynamic imports. Even though dynamic import is stage 3, Webpack has supported a version of it for quite some time code splitting.

/* Foo.re */
let x = 5;

/* Importer.re */
import(Foo /* maybe just a relative path? */) 
|> Js.Promise.then_(/* hand waving */);

would generate the following code

import ('./Foo.js').then(foo => foo.x);

Most helpful comment

Here's a hacky workaround that relies on BuckleScript's nice feature of not mangling names.

/** Dataloader.re - just a binding to Facebook's Dataloader for demonstration purposes */

/** The type of the dynamically-imported module. */
type dynamic;

/** The [Dataloader] type. */
type t('a, 'b);

/** [import()] generates a dynamic import to [dataloader].
    {i PLEASE ENSURE} you use it like: {[
Dataloader.import() |> Js.Promise.then_(_dataloader => ...)
    ]}

    The [_dataloader] is especially important because it generates code
    that works with the rest of the below externals. */
[@bs.val] external import: (
  [@bs.as {json|"dataloader"|json}] _,
  unit
) => Js.Promise.t(dynamic) = "import";

type batchFn('a, 'b) = (. array('a)) => Js.Promise.t(array('b));
[@bs.new] external make: batchFn('a, 'b) => t('a, 'b) = "_dataloader.Dataloader";

/* Use.re */

let loader: Js.Promise.t(Dataloader.t(int, int)) = Dataloader.import()
  |> Js.Promise.then_(_dataloader => /* [_dataloader] naming is important here */
    Js.Promise.resolve(Dataloader.make((. array) =>
      Js.Promise.resolve(array)))
  );

This generates the output:

// Generated by BUCKLESCRIPT VERSION 4.0.14, PLEASE EDIT WITH CARE
'use strict';


var loader = import(("dataloader")).then((function (_dataloader) {
        return Promise.resolve(new _dataloader.Dataloader((function (array) {
                          return Promise.resolve(array);
                        })));
      }));

exports.loader = loader;
/* loader Not a pure module */

All 10 comments

If you have a webpack loader for buckelscript you can just compile it down to the JavaScript import() and Webpack will handle the compilation.

@xtuc this is not ideal. Not everyone uses webpack, nor should they be forced to if they are using bucklescript. I think this is more of, "Please support this within bucklescript platform itself" Just like bucklescript can support outputting ES Modules and CommonJS.

import(Foo /* maybe just a relative path? */)

Supporting dynamic import like this might be tricky. In a browser environment, I don't know how BuckleScript would know what path to render, as it doesn't know the asset urls your server responds to. Also, what happens when you dynamically load an npm module?.

My personal feature wish is for BuckleScript to support import : string => Js.Promise.t(???) where we can, somehow, typecast the ??? to a module of our choice.

right now I think things could unlock pretty easily:

/* X.re */
let x = 24;
/* App.re */
module type XT = {
  include (module type of {
             include X;
           });
};

/* the external is pretty stupid but it's just for demonstration purposes */
[@bs.val] external import : string => Js.Promise.t((module XT)) = "";

import("./X.bs")
|> Js.Promise.then_((res: (module XT)) => {
     module Y = (val res);
     Js.log(Y.x);
     Js.Promise.resolve();
   });

outputs

// Generated by BUCKLESCRIPT VERSION 2.2.3, PLEASE EDIT WITH CARE
'use strict';


import("./X").then((function (res) {
        console.log(res[/* x */0]);
        return Promise.resolve(/* () */0);
      }));

/*  Not a pure module */

It'd only require BuckleScript to understand that in case X is a file, accessing its exports (here x) is through importedX##x and not importedX[0]

My use case is more simple - I just need to import a json dynamically using webpack, but because [%%bs.raw] doesn't support dynamic strings, I can't follow the advice presented in the bsb docs. Is there a different way?

@outkine Webpack's dynamic import can't accept arbitrary strings at runtime either. It only works with hard-coded strings.

Here's a hacky workaround that relies on BuckleScript's nice feature of not mangling names.

/** Dataloader.re - just a binding to Facebook's Dataloader for demonstration purposes */

/** The type of the dynamically-imported module. */
type dynamic;

/** The [Dataloader] type. */
type t('a, 'b);

/** [import()] generates a dynamic import to [dataloader].
    {i PLEASE ENSURE} you use it like: {[
Dataloader.import() |> Js.Promise.then_(_dataloader => ...)
    ]}

    The [_dataloader] is especially important because it generates code
    that works with the rest of the below externals. */
[@bs.val] external import: (
  [@bs.as {json|"dataloader"|json}] _,
  unit
) => Js.Promise.t(dynamic) = "import";

type batchFn('a, 'b) = (. array('a)) => Js.Promise.t(array('b));
[@bs.new] external make: batchFn('a, 'b) => t('a, 'b) = "_dataloader.Dataloader";

/* Use.re */

let loader: Js.Promise.t(Dataloader.t(int, int)) = Dataloader.import()
  |> Js.Promise.then_(_dataloader => /* [_dataloader] naming is important here */
    Js.Promise.resolve(Dataloader.make((. array) =>
      Js.Promise.resolve(array)))
  );

This generates the output:

// Generated by BUCKLESCRIPT VERSION 4.0.14, PLEASE EDIT WITH CARE
'use strict';


var loader = import(("dataloader")).then((function (_dataloader) {
        return Promise.resolve(new _dataloader.Dataloader((function (array) {
                          return Promise.resolve(array);
                        })));
      }));

exports.loader = loader;
/* loader Not a pure module */

@bobzhang maybe close this issue and consolidate discussion to:
https://github.com/BuckleScript/bucklescript/issues/3919

Was this page helpful?
0 / 5 - 0 ratings