Research
Security News
Threat Actor Exposes Playbook for Exploiting npm to Build Blockchain-Powered Botnets
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
bs-dynamic-import
Advanced tools
📦🚀 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.
npm install bs-dynamic-import --save
Then add it to "bsconfig.json" :
"bs-dependencies": [
"bs-dynamic-import"
]
You can now use "DynamicImport" module.
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.
Some of the most common problematic patterns that were covered include :
Server-side (Node.js) : Node.js doesn't support dynamic import, you should use Babel with "babel-plugin-dynamic-import-node". #example
Client-side (web) : you should use a bundler (Webpack 4 and Parcel support dynamic import with zero-configuration, Rollup require experimental flag). #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).
First, we declare a module type who refer to Math module type himself.
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.
"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.
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))
);
If you want to import multiple module in parallel, there is multiple resolve function who work with tuple :
/* 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))
);
Dynamic module type.
Import dynamic module.
Resolve dynamic module.
There is resolve2, resolve3, resolve4, resolve5, resolve6 that do the same thing with tuple for parallel import.
We expose 6 infix operator for better experience :
Underlying, these operator work with any "Js.Promise.t".
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 */
)
);
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 */
);
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 ..."))
);
FAQs
BuckleScript dynamic import interop on JavaScript environment
The npm package bs-dynamic-import receives a total of 20 weekly downloads. As such, bs-dynamic-import popularity was classified as not popular.
We found that bs-dynamic-import demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
Security News
NVD’s backlog surpasses 20,000 CVEs as analysis slows and NIST announces new system updates to address ongoing delays.
Security News
Research
A malicious npm package disguised as a WhatsApp client is exploiting authentication flows with a remote kill switch to exfiltrate data and destroy files.