All Projects → kMeillet → bs-dynamic-import

kMeillet / bs-dynamic-import

Licence: MIT license
📦🚀 BuckleScript dynamic import interop on JavaScript environment

Programming Languages

javascript
184084 projects - #8 most used programming language
reason
219 projects

Projects that are alternatives of or similar to bs-dynamic-import

Reason Loadable
🔥 Suspense/Lazy for ReasonReact.
Stars: ✭ 88 (+183.87%)
Mutual labels:  dynamic, code-splitting, import, reasonml
Hr4r
Example project - "Hot Reloading 4 RequireJS" front-end web applications & some extra code demonstrating hot-reloading for Node.js Express servers
Stars: ✭ 28 (-9.68%)
Mutual labels:  dynamic, code-splitting, lazy-loading
Webpack Require From
Webpack plugin that allows to configure path or URL for fetching dynamic imports
Stars: ✭ 142 (+358.06%)
Mutual labels:  dynamic, code-splitting, import
guide-to-async-components
📖 Guide To JavaScript Async Components
Stars: ✭ 79 (+154.84%)
Mutual labels:  code-splitting, lazy-loading
Preact Lazy Route
Lazy load preact route components
Stars: ✭ 12 (-61.29%)
Mutual labels:  code-splitting, lazy-loading
reason-react-lazy-loading
Example project to show how to use components lazy loading in ReasonReact
Stars: ✭ 41 (+32.26%)
Mutual labels:  lazy-loading, reasonml
Redux Dynamic Modules
Modularize Redux by dynamically loading reducers and middlewares.
Stars: ✭ 874 (+2719.35%)
Mutual labels:  dynamic, code-splitting
get-css-data
A micro-library for collecting stylesheet data from link and style nodes
Stars: ✭ 29 (-6.45%)
Mutual labels:  module, import
lazy-load
🌅 Lazy Loading module for Nuxt 3
Stars: ✭ 30 (-3.23%)
Mutual labels:  module, lazy-loading
consulator
Import and synchronize your Consul KV data from JSON and YAML
Stars: ✭ 27 (-12.9%)
Mutual labels:  import
fury-kubernetes-opa
Kubernetes Fury OPA. Policy enforcement for your Kubernetes Cluster
Stars: ✭ 34 (+9.68%)
Mutual labels:  module
rekernel
A minimal setup for writing Unikernels in ReasonML
Stars: ✭ 28 (-9.68%)
Mutual labels:  reasonml
MeetupPS
PowerShell module to interact with Meetup.com API
Stars: ✭ 15 (-51.61%)
Mutual labels:  module
lazysimon
Minimal effort 350 byte JavaScript library to lazy load all <img> on your website
Stars: ✭ 18 (-41.94%)
Mutual labels:  lazy-loading
bump
a tiny tool to bump nimble versions 🍻
Stars: ✭ 23 (-25.81%)
Mutual labels:  module
tracker-issues
Issue tracking system and Workflow documents integrated to Humhub
Stars: ✭ 28 (-9.68%)
Mutual labels:  module
turaku-client
Web / Electron client application for Turaku
Stars: ✭ 27 (-12.9%)
Mutual labels:  reasonml
bacen-exchange-rates
Reason API to fetch the daily reports from BACEN PTAX exchanges
Stars: ✭ 16 (-48.39%)
Mutual labels:  reasonml
react-rules-of-hooks-ppx
This ppx validates the rules of React hooks.
Stars: ✭ 40 (+29.03%)
Mutual labels:  reasonml
deployserver
Deploy your project automatically when git branch was updated.
Stars: ✭ 24 (-22.58%)
Mutual labels:  module

Summary

Build Status NPM license

Warning : This package is now in read-only mode, if you want to lazy load React components in a ReasonML application use "reason-loadable".

If you want to import something else like a JSON document or JavaScript code that isn't a React component (example : Math library), take a look at "reason-loadable" source code and tests, it can do that with some adaptation.

This package probably doesn't work anymore since BuckleScript changed object representation in JavaScript output in recent version (from array to object).


📦🚀 BuckleScript dynamic import interop on JavaScript environment.

Provide a clear path for Reason/Ocaml module to become importable at runtime, preserve type-safety.

Note : This project does not target native compilation but JavaScript compilation.

Installation

npm install bs-dynamic-import --save

Then add it to "bsconfig.json" :

"bs-dependencies": [
 "bs-dynamic-import"
]

You can now use "DynamicImport" module.

Motivation

I will try to explain my propose and the following pattern for dynamic import API in Reason/Ocaml.

The existing syntactic forms for JavaScript importing modules are static declarations. They accept a string literal as the module specifier, and introduce bindings into the local scope via a pre-runtime "linking" process.

This is a great design for the 90% case, and supports important use cases such as static analysis, bundling tools, and tree shaking.

However, it's also desirable to be able to dynamically load parts of a JavaScript application at runtime. This could be because of factors only known at runtime (such as the user's language), for performance reasons (not loading code until it is likely to be used), or for robustness reasons (surviving failure to load a non-critical module). Such dynamic code-loading has a long history, especially on the web, but also in Node.js (to delay startup costs). The existing import syntax does not support such use cases ...

Truly dynamic code loading also enables advanced scenarios, such as racing multiple modules against each other and choosing the first to successfully load.

https://tc39.github.io/proposal-dynamic-import/

In Reason/Ocaml, every file is a module : file name map to module name and you can make module into module. With BuckleScript, we can compile Reason/Ocaml module to JavaScript module.

BuckleScript doesn't provide dynamic import.

🔥 "bs-dynamic-import" let you use dynamic import right now with BuckleScript.

When BuckleScript will release dynamic import support, you should drop "bs-dynamic-import" and switch to BuckleScript syntax ; no worries, this project offers a basic API and can be replaced very quickly if needed.

Common problems

Some of the most common problematic patterns that were covered include :

  • Commonjs/ESM support. ✔️
  • Dynamic import Reason/Ocaml module. ✔️
  • Dynamic import multiple module in parallel. ✔️
  • Race module against each other.

Support

Example

Basic example

Consider a Math module and Main module :

/* Math.re */
let addOne = x => x + 1;
/* ... */
/* Main.re */
3 |> Math.addOne |> Js.log; /* 4 */

Note : Pipe operator "|>" help chaining function and avoid parenthesis ceremony.

Module are static (know at compile time). If you want to import Math dynamically (on runtime), use "DynamicImport" module.

/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Math.bs.js")
  |> resolve
  |> Js.Promise.then_(((module AnonymouModule): (module MathType)) =>
      3 |> AnonymouModule.addOne |> Js.log |> Js.Promise.resolve /* 4 */
  )
);

Note : You must always import BuckleScript output (bs.js), don't try to import Reason/Ocaml file (that will not work).

  1. First, we declare a module type who refer to Math module type himself.

  2. We open "DynamicImport" module locally to avoid name collision. "DynamicImport" module come with multiple functions and infix operator but the most important part of the API are "import" and "resolve" function.

  3. "DynamicImport.import" share signature with dynamic import JavaScript API : take a module path and return a Promise of module. This Promise should be passed to "DynamicImport.resolve" when you want to resolve module.

Note : when using "DynamicImport.import" you should provide ".bs.js" extension (or configure your bundler to recognize ".bs.js" extension as ".js" extension).

Note : if you import wrong module or a path who doesn't exist, compiler will not complain so be careful about this situation when you move/rename file, like with JavaScript module.

  1. After module is dynamically imported, you can use "Js.Promise.then_" and "repack" the anonymous module with correct module type. If you use wrong module type or forgot to provide it, you will face compiler error.

Note : Using "Js.Promise.then_" is verbose (you have to wrap result with "Js.Promise.resolve" every time), we provide "<$>" (map) operator to traverse Promise and apply your function (return value will be wrapped into Promise).

/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Math.bs.js")
  |> resolve
  <$> (
    ((module AnonymouModule): (module MathType)) =>
      3 |> AnonymouModule.addOne |> Js.log /* 4 */
  )
);

🔥 Look much better !

Finally, you can catch error with "Js.Promise.catch" - and of course we provide "<$!>" (map catch) operator (return value will be wrapped into Promise).

/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Math.bs.js")
  |> resolve
  <$> (
    ((module AnonymouModule): (module MathType)) =>
      3 |> AnonymouModule.addOne |> Js.log /* 4 */
  )
  <$!> (error => Js.log(error))
);

Now, how can we dynamically import JavaScript library ? Write 1:1 binding like normal way and expose what you want.

npm install ramda --save
/* Ramda.re */
[@bs.module "ramda"] external inc : int => int = "inc";

let inc = inc;
/* Main.re */
module type RamdaType = (module type of Ramda);

DynamicImport.(
  import("./Ramda.bs.js")
  |> resolve
  <$> (
    ((module Ramda): (module RamdaType)) =>
      3 |> Ramda.inc |> Js.log /* 4 */
  )
  <$!> (error => Js.log(error))
);

What about default export compatibility ?

/* Math.re */
let addOne = x => x + 1;
/* ... */
let default = () => "Default export !";
/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Math.bs.js")
  |> resolve
  <$> (
    ((module Math): (module MathType)) =>
      Ramda.default() |> Js.log /* "Default export !" */
  )
  <$!> (error => Js.log(error))
);

Multiple module

If you want to import multiple module in parallel, there is multiple resolve function who work with tuple :

  • DynamicImport.resolve2
  • DynamicImport.resolve3
  • DynamicImport.resolve4
  • DynamicImport.resolve5
  • DynamicImport.resolve6
/* Main.re */
module type MathType = (module type of Math);
module type CurrencyType = (module type of Currency);

DynamicImport.(
  resolve2((
    import("./Math.bs.js"),
    import("./Currency.bs.js")
  ))
  <$> (
    (
      (
        (module Math): (module MathType),
        (module Currency): (module CurrencyType)
      )
    ) =>
      3
      |> Math.addOne
      |> Currency.toDollar
      |> Js.log
  )
  <$!> (error => Js.log(error))
);

API

DynamicImport

API

type importable('a)

Dynamic module type.

import: string => Js.Promise.t(importable('a))

Import dynamic module.

resolve: Js.Promise.t(importable('a)) => Js.Promise.t('a)

Resolve dynamic module.

There is resolve2, resolve3, resolve4, resolve5, resolve6 that do the same thing with tuple for parallel import.

Infix

We expose 6 infix operator for better experience :

  • <$> (Promise map).
  • <$!> (Promise map catch).
  • >>= (Promise then).
  • =<< (Reverse Promise then).
  • >>=! (Promise catch).
  • !=<< (Reverse Promise catch).

Underlying, these operator work with any "Js.Promise.t".

Common errors

"The signature for this packaged module couldn't be inferred."

This error mean you forgot to provide module type on resolved module.

Wrong :

/* Main.re */
DynamicImport.(
  import("./Math.bs.js")
  |> resolve
  <$> (
    ((module Math)) => /* Signature missing ! */
      3 |> Math.addOne |> Js.log /* 4 */
  )
);

✔️ Good :

/* Main.re */
module type MathType = (module type of Math); /* Signature */

DynamicImport.(
  import("./Math.bs.js")
  |> resolve
  <$> (
    ((module Math): (module MathType)) => /* Provide signature */
      3 |> Math.addOne |> Js.log /* 4 */
  )
);

"The value >>= can't be found."

You should use local or global open to have "DynamicImport" module in scope.

Wrong :

/* Main.re */
module type MathType = (module type of Math);

/* Where is DynamicImport ? */
import("./Math.bs.js")
|> resolve
<$> (
  ((module Math): (module MathType)) =>
    3 |> Math.addOne |> Js.log /* 4 */
);

✔️ Good :

/* Main.re */
module type MathType = (module type of Math);

/* Local open */
DynamicImport.(
  import("./Math.bs.js")
  |> resolve
  <$> (
    ((module Math): (module MathType)) =>
    3 |> Math.addOne |> Js.log /* 4 */
  )
);
/* Main.re */
/* Global open */
open DynamicImport;

module type MathType = (module type of Math);

import("./Math.bs.js")
|> resolve
<$> (
  ((module Math): (module MathType)) =>
    3 |> Math.addOne |> Js.log /* 4 */
);

"JavaScript runtime type error (cannot find length, 0 is not a function)" (nightmare !)

Compiler can not verify that you have imported the right module or check if module path is correct.

That's your responsability and you should be cautious about this because it's very error prone.

Always import file that will be compiled by BuckleScript (".bs.js" file), never import Reason/Ocaml file.

You can catch any error with "<$!>" operator (map catch) and apply custom logic if something fail on runtime.

Wrong :

/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Mat.bs.js") /* Bad path, file is missing */
  |> resolve
  <$> (
    ((module Math): (module MathType)) =>
      3 |> Math.addOne |> Js.log /* 4 */
  )
);
/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Math.re") /* Can't, Reason file */
  |> resolve
  <$> (
    ((module Math): (module MathType)) =>
      3 |> Math.addOne |> Js.log /* 4 */
  )
);
/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Math.js") /* Can't, non-BuckleScript output */
  |> resolve
  <$> (
    ((module Math): (module MathType)) =>
      3 |> Math.addOne |> Js.log /* 4 */
  )
);
/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Math.ml") /* Can't, Ocaml file */
  |> resolve
  <$> (
    ((module Math): (module MathType)) =>
      3 |> Math.addOne |> Js.log /* 4 */
  )
);

✔️ Good :

```reason
/* Main.re */
module type MathType = (module type of Math);

DynamicImport.(
  import("./Math.bs.js") /* Can, BuckleScript output from Reason/Ocaml module */
  |> resolve
  <$> (
    ((module Math): (module MathType)) =>
      3 |> Math.addOne |> Js.log /* 4 */
  )
  <$!> ((_error) => Js.log("Something goes wrong, reloading ..."))
);
/* Main.re */
module type BsMathType = (module type of BsMath);

DynamicImport.(
  import("bs-math") /* Can, BuckleScript output from Reason/Ocaml module */
  |> resolve
  <$> (
    ((module BsMath): (module BsMathType)) =>
      3 |> BsMath.sqrt |> Js.log /* 1.73 */
  )
  <$!> ((_error) => Js.log("Something goes wrong, reloading ..."))
);
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].