|
| 1 | +--- |
| 2 | +author: rescript-team |
| 3 | +date: "2023-06-05" |
| 4 | +title: First-class Dynamic Import Support |
| 5 | +badge: roadmap |
| 6 | +description: | |
| 7 | + A tour of new capabilities coming to ReScript v11 |
| 8 | +--- |
| 9 | + |
| 10 | +> This is the third post covering new capabilities that'll ship in ReScript v11. You can check out the first post on [Better Interop with Customizable Variants](/blog/improving-interop) and the second post on [Enhanced Ergonomics for Record Types](/blog/enhanced-ergonomics-for-record-types). |
| 11 | +
|
| 12 | +## Introduction |
| 13 | + |
| 14 | +When developing apps in JavaScript, every line of code eventually needs to be bundled up and shipped to the browser. As the app grows, it's usually a good idea to split up and load parts of the app code on demand as separate JS modules to prevent bundle bloat. |
| 15 | + |
| 16 | +To accomplish this, browsers provide support for dynamic loading via the globally available [`import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) function to allow code splitting and lazy loading and ultimately reducing initial load times for our applications. |
| 17 | + |
| 18 | +Even though ReScript has been able to bind to `import` calls via `external` bindings, doing so was quite hard to maintain for to the following reasons: |
| 19 | + |
| 20 | +1. An `import` call requires a path to a JS file. The ReScript compiler doesn't directly expose file paths for compiled modules, so the user has to manually find and rely on compiled file paths. |
| 21 | +2. The return type of an `import` call needs to be defined manually; a quite repetitive task with lots of potential bugs when the imported module has changed. |
| 22 | + |
| 23 | +Arguably, these kind of problems should ideally be tackled on the compiler level, since the ReScript compiler knows best about module structures and compiled JS file locations — so we finally decided to fix this. |
| 24 | + |
| 25 | +Today we're happy to announce that ReScript v11 will ship with first-class support for dynamic imports as part of the language. |
| 26 | + |
| 27 | +Let's have a look! |
| 28 | + |
| 29 | +## Import Parts of a Module |
| 30 | + |
| 31 | +We can now use the `Js.import` function to dynamically import a value or function from a ReScript module. The import call will return a promise, resolving to the dynamically loaded value. |
| 32 | + |
| 33 | +For example, imagine the following file `MathUtils.res`: |
| 34 | + |
| 35 | +```rescript |
| 36 | +// MathUtils.res |
| 37 | +let add = (a, b) => a + b |
| 38 | +let sub = (a, b) => a - b |
| 39 | +``` |
| 40 | + |
| 41 | +Now let's dynamically import the `add` function in another module, e.g. `App.res`: |
| 42 | + |
| 43 | +```rescript |
| 44 | +// App.res |
| 45 | +let main = async () => { |
| 46 | + let add = await Js.import(MathUtils.add) |
| 47 | + let onePlusOne = add(1, 1) |
| 48 | +
|
| 49 | + RescriptCore.Console.log(onePlusOne) |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +This compiles to: |
| 54 | + |
| 55 | +```javascript |
| 56 | +async function main() { |
| 57 | + var add = await import("./MathUtils.mjs").then(function(m) { |
| 58 | + return m.add; |
| 59 | + }); |
| 60 | + |
| 61 | + var onePlusOne = add(1, 1); |
| 62 | + console.log(onePlusOne); |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +Notice how the compiler keeps track of the relative path to the module you're importing, as well as plucking out the value you want to use from the imported module. |
| 67 | + |
| 68 | +Quite a difference compared to doing both of those things manually, right? Now let's have a look at a more concrete use-case with React components. |
| 69 | + |
| 70 | +### Use-case: Importing a React component |
| 71 | + |
| 72 | +> **Note:** This section requires the latest [@rescript/react](https://github.com/rescript-lang/rescript-react) bindings to be installed (_0.12.0-alpha.2 and above_). |
| 73 | +
|
| 74 | +Our dynamic import makes tasks like [lazy loading React components](https://react.dev/reference/react/lazy#lazy) a simple one-liner. First let's define a simple component as an example: |
| 75 | + |
| 76 | +```rescript |
| 77 | +// Title.res |
| 78 | +@react.component |
| 79 | +let make = (~text) => { |
| 80 | + <div className="title">{text->React.string}</div> |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +Now let's dynamically import the `<Title/>` component by passing the result of our dynamic import to `React.lazy_`: |
| 85 | + |
| 86 | +```rescript |
| 87 | +module LazyTitle = { |
| 88 | + let make = React.lazy_(() => Js.import(Title.make)) |
| 89 | +} |
| 90 | +
|
| 91 | +let titleJsx = <LazyTitle text="Hello!" /> |
| 92 | +``` |
| 93 | + |
| 94 | +That's all the code we need! The new `<LazyTitle />` component behaves exactly the same as the wrapped `<Title />` component, but will be lazy loaded via React's built in lazy mechanism. |
| 95 | + |
| 96 | +Needless to say, all the code examples you've seen so far are fully type-safe. |
| 97 | + |
| 98 | +## Import a Whole Module |
| 99 | + |
| 100 | +Sometimes it is useful to dynamically import the whole module instead. For example, you might have a collection of utility functions in a dedicated module that tend to be used together. |
| 101 | + |
| 102 | +The syntax for importing a whole module looks a little different, since we are operating on the module syntax level; instead of using `Js.import`, you may simply `await` the module itself: |
| 103 | + |
| 104 | +```rescript |
| 105 | +// App.res |
| 106 | +let main = async () => { |
| 107 | + module Utils = await MathUtils |
| 108 | +
|
| 109 | + let twoPlusTwo = Utils.add(2, 2) |
| 110 | + RescriptCore.Console.log(twoPlusTwo) |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +And, the generated JavaScript will look like this: |
| 115 | + |
| 116 | +```js |
| 117 | +async function main() { |
| 118 | + var Utils = await import("./MathUtils.mjs"); |
| 119 | + |
| 120 | + var twoPlusTwo = Utils.add(2, 2); |
| 121 | + console.log(twoPlusTwo); |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +The compiler correctly inserts the module's import path and stores the result in a `Utils` variable. |
| 126 | + |
| 127 | +## Try it out! |
| 128 | + |
| 129 | +Feel free to try out our new dynamic import feature with the latest beta release: |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | +Please note that this release is only intended for experiments and feedback purposes. |
| 134 | + |
| 135 | + |
| 136 | +## Conclusion |
| 137 | + |
| 138 | +The most important take away of the new dynamic imports functionality in ReScript is that you'll never need to care about _where_ what you're importing is located on the file system - the compiler already does it for you. |
| 139 | + |
| 140 | +We hope that it will help shipping software with better end-user experience with faster load times and quicker app interaction, especially on slower network connections. |
| 141 | + |
| 142 | +As always, we're eager to hear about your experiences with our new features. Feel free to share your thoughts and feedback with us on our [issue tracker](https://github.com/rescript-lang/rescript-compiler/issues) or on the [forum](https://forum.rescript-lang.org). |
| 143 | + |
| 144 | +Happy hacking! |
0 commit comments