|
| 1 | +--- |
| 2 | +author: rescript-team |
| 3 | +date: "2023-02-02" |
| 4 | +title: ReScript 10.1 |
| 5 | +badge: release |
| 6 | +description: | |
| 7 | + Async/await & better Promise support, JSX v4, and more! |
| 8 | +--- |
| 9 | + |
| 10 | +## Introduction |
| 11 | + |
| 12 | +We are happy to announce ReScript 10.1! |
| 13 | + |
| 14 | +ReScript is a robustly typed language that compiles to efficient and human-readable JavaScript. It comes with one of the fastest build toolchains and offers first class support for interoperating with ReactJS and other existing JavaScript code. |
| 15 | + |
| 16 | +Use `npm` to install the newest [10.1 release](https://www.npmjs.com/package/rescript/v/10.1.2): |
| 17 | + |
| 18 | +``` |
| 19 | +npm install rescript |
| 20 | +
|
| 21 | +# or |
| 22 | +
|
| 23 | + |
| 24 | +``` |
| 25 | + |
| 26 | +This version comes with two major language improvements we've all been waiting for. **async/await support** for an easy way to write asynchronous code in a synchronous manner, and a **new JSX transform** with better ergonomics, code generation and React 18 support. |
| 27 | + |
| 28 | +Alongside the major changes, there have been many bugfixes and other improvements that won't be covered in this post. |
| 29 | + |
| 30 | +Feel free to check the [Changelog](https://github.com/rescript-lang/rescript-compiler/blob/master/CHANGELOG.md#1011) for all the details. |
| 31 | + |
| 32 | +## New `async` / `await` syntax |
| 33 | + |
| 34 | +Async / await has arrived. Similar to its JS counterparts, you are now able to define `async` functions and use the `await` operator to unwrap a promise value. This allows writing asynchronous code in a synchronous fashion. |
| 35 | + |
| 36 | +**Example:** |
| 37 | + |
| 38 | +```res |
| 39 | +// Some fictive functionality that offers asynchronous network actions |
| 40 | +@val external fetchUserMail: string => promise<string> = "GlobalAPI.fetchUserMail" |
| 41 | +@val external sendAnalytics: string => promise<unit> = "GlobalAPI.sendAnalytics" |
| 42 | +
|
| 43 | +// We use the `async` keyword to allow the use of `await` in the function body |
| 44 | +let logUserDetails = async (userId: string) => { |
| 45 | + // We use `await` to fetch the user email from our fictive user endpoint |
| 46 | + let email = await fetchUserMail(userId) |
| 47 | +
|
| 48 | + await sendAnalytics(`User details have been logged for ${userId}`) |
| 49 | +
|
| 50 | + Js.log(`Email address for user ${userId}: ${email}`) |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +To learn more about our async / await feature, check out the relevant [manual section](/docs/manual/latest/async-await). |
| 55 | + |
| 56 | +## New `promise` builtin type and `Js.Promise2` module |
| 57 | + |
| 58 | +In previous versions of ReScript, promises were expressed as a `Js.Promise.t<'a>` type, which was a little tedious to type. From now on, users may use the `promise<'a>` type instead. |
| 59 | + |
| 60 | +Quick example of a `.resi` file using the new `promise` type: |
| 61 | + |
| 62 | +```resi |
| 63 | +// User.resi |
| 64 | +type user |
| 65 | +
|
| 66 | +let fetchUser: string => promise<user> |
| 67 | +``` |
| 68 | + |
| 69 | +Way easier on the eyes, don't you think? Note that the new `promise` type is fully compatible with `Js.Promise.t` (no breaking changes). |
| 70 | + |
| 71 | +Additionally, we also introduced the `Js.Promise2` module as a stepping stone to migrate `Js.Promise` based code to a first-pipe (->) friendly solution. For the daily practise you'll almost always want to use `async` / `await` to handle promises. |
| 72 | + |
| 73 | +(*Sidenote*: We are also well aware that our users want a solution to unify `Belt`, `Js` and `Js.xxx2` and have a fully featured "standard library" instead of adding more `Js.xxx2` modules. Good news is that we have a solution in the pipeline to fix this. `Js.Promise2` was introduced to ease the process later on and is not supposed to be the panacea of promise handling.) |
| 74 | + |
| 75 | +If you are already using a third-party promise library like [ryyppy/rescript-promise](https://github.com/ryyppy/rescript-promise) or similar, there's no need to migrate any existing code. Introduce `async` / `await` gradually in your codebase as you go. |
| 76 | + |
| 77 | + |
| 78 | +## New JSX v4 syntax |
| 79 | + |
| 80 | +ReScript 10.1 now ships with JSX v4. Here's what's new: |
| 81 | + |
| 82 | +- **Cleaner interop.** Due to recent improvements in the type checker, the `@react.component` transformation doesn't require any `makeProps` convention anymore. `make` functions will now be transformed into a `prop` type and a component function. That's it. |
| 83 | +- **Two new transformation modes **. JSX v4 comes with a `classic` mode (= `React.createElement`) and `automatic` mode (= `jsx-runtime` calls). The latter is the new default, moving forward with `rescript/[email protected]` and `React@18`. |
| 84 | +- **Allow mixing JSX configurations on the project and module level.** Gradually mix and match JSX transformations and modes without migrating any old code! |
| 85 | +- **Pass `prop` types** to `@react.component`. You can now fine tune `@react.component` with your specific prop type needs. Very useful for libraries and frameworks to define component interfaces. |
| 86 | +- **Less boilerplate when using `React.Context`**. Check out our [example](/docs/react/latest/migrate-react#reactcontext) for comparison. |
| 87 | +- **Revisited props spread operator. ** This will allow users to spread records in JSX without sacrificing their sanity. Note that this implementation has harder constraints than its JS counterpart. (requires `rescript/[email protected]` or higher) |
| 88 | +- **Better type inference of props.** Type inference when passing e.g. variants that are defined in the same module as the component is much improved. With the earlier JSX version, you'd often need to write code like this in order for the compiler to understand which variant you're passing: `<Button variant=Button.Primary text="Click" />`. With JSX v4, you won't need to tell the compiler where the variant you're passing is located: `<Button variant=Primary text="Click" />`. |
| 89 | + |
| 90 | +Code tells more than words, so here's a non-exhaustive code example to highlight the different JSX features. Make sure to also check out the JS output and play around with the code in our newest playground! |
| 91 | + |
| 92 | +<CodeTab labels={["ReScript", "JS Output"]}> |
| 93 | + |
| 94 | +```res |
| 95 | +// Set the jsx configuration per module |
| 96 | +@@jsxConfig({version: 4, mode: "automatic"}) |
| 97 | +
|
| 98 | +module AutomaticModeExample = { |
| 99 | + // "automatic" mode will compile jsx to the React 18 compatible |
| 100 | + // jsx-runtime calls |
| 101 | + @@jsxConfig({version: 4, mode: "automatic"}) |
| 102 | +
|
| 103 | + @react.component |
| 104 | + let make = (~name) => { |
| 105 | + <div> {React.string(`Hello ${name}`)} </div> |
| 106 | + } |
| 107 | +} |
| 108 | +
|
| 109 | +module ClassicModeExample = { |
| 110 | + // "classic" mode will compile jsx to React.createElement calls |
| 111 | + @@jsxConfig({version: 4, mode: "classic"}) |
| 112 | +
|
| 113 | + @react.component |
| 114 | + let make = (~name) => { |
| 115 | + <div> {React.string(`Hello ${name}`)} </div> |
| 116 | + } |
| 117 | +} |
| 118 | +
|
| 119 | +module NoAttributeExample = { |
| 120 | + // No need for `makeProps` anymore |
| 121 | + type props = {name: string} |
| 122 | +
|
| 123 | + let make = (props: props) => { |
| 124 | + <div> {React.string(`Hello ${props.name}`)} </div> |
| 125 | + } |
| 126 | +} |
| 127 | +
|
| 128 | +module ReactInterfaceExample: { |
| 129 | + @react.component |
| 130 | + let make: (~name: string, ~age: int=?) => React.element |
| 131 | +} = { |
| 132 | + @react.component |
| 133 | + let make = (~name, ~age=0) => { |
| 134 | + <div> |
| 135 | + {React.string( |
| 136 | + `Hello ${name}, you are ${Belt.Int.toString(age)} years old.`, |
| 137 | + )} |
| 138 | + </div> |
| 139 | + } |
| 140 | +} |
| 141 | +
|
| 142 | +module PropTypeInjectionExample = { |
| 143 | + // Let's assume we have a prop type that we wanna enforce |
| 144 | + // as our labeled arguments |
| 145 | + type someoneElsesProps = {isHuman: bool} |
| 146 | +
|
| 147 | + // Here we tell the `react.component` decorator what props to infer. |
| 148 | + // Useful for e.g. NextJS usage, or to create components that should |
| 149 | + // comply to certain library component interfaces |
| 150 | + @react.component(: someoneElsesProps) |
| 151 | + let make = (~isHuman) => { |
| 152 | + let msg = switch isHuman { |
| 153 | + | true => "hello human" |
| 154 | + | false => "hello fellow computer" |
| 155 | + } |
| 156 | + <div> {React.string(msg)} </div> |
| 157 | + } |
| 158 | +} |
| 159 | +
|
| 160 | +module PropSpreadExample = { |
| 161 | + // Note: This will require @rescript/react 0.11 or later |
| 162 | + @@jsxConfig({version: 4, mode: "automatic"}) |
| 163 | +
|
| 164 | + @react.component |
| 165 | + let make = () => { |
| 166 | + let props = {NoAttributeExample.name: "World"} |
| 167 | +
|
| 168 | + <NoAttributeExample {...props} /> |
| 169 | + } |
| 170 | +} |
| 171 | +
|
| 172 | +let root = |
| 173 | + <div> |
| 174 | + <AutomaticModeExample name="Automatic" /> |
| 175 | + <ClassicModeExample name="Classic" /> |
| 176 | + <NoAttributeExample name="NoAttribute" /> |
| 177 | + <ReactInterfaceExample name="Interface" /> |
| 178 | + <PropTypeInjectionExample isHuman=true /> |
| 179 | + <PropSpreadExample /> |
| 180 | + </div> |
| 181 | +``` |
| 182 | + |
| 183 | +```js |
| 184 | +import * as React from "react"; |
| 185 | +import * as JsxRuntime from "react/jsx-runtime"; |
| 186 | + |
| 187 | +function Playground$AutomaticModeExample(props) { |
| 188 | + return JsxRuntime.jsx("div", { |
| 189 | + children: "Hello " + props.name + "" |
| 190 | + }); |
| 191 | +} |
| 192 | + |
| 193 | +var AutomaticModeExample = { |
| 194 | + make: Playground$AutomaticModeExample |
| 195 | +}; |
| 196 | + |
| 197 | +function Playground$ClassicModeExample(props) { |
| 198 | + return React.createElement("div", undefined, "Hello " + props.name + ""); |
| 199 | +} |
| 200 | + |
| 201 | +var ClassicModeExample = { |
| 202 | + make: Playground$ClassicModeExample |
| 203 | +}; |
| 204 | + |
| 205 | +function make(props) { |
| 206 | + return JsxRuntime.jsx("div", { |
| 207 | + children: "Hello " + props.name + "" |
| 208 | + }); |
| 209 | +} |
| 210 | + |
| 211 | +var NoAttributeExample = { |
| 212 | + make: make |
| 213 | +}; |
| 214 | + |
| 215 | +function Playground$ReactInterfaceExample(props) { |
| 216 | + var age = props.age; |
| 217 | + var age$1 = age !== undefined ? age : 0; |
| 218 | + return JsxRuntime.jsx("div", { |
| 219 | + children: "Hello " + props.name + ", you are " + String(age$1) + " years old." |
| 220 | + }); |
| 221 | +} |
| 222 | + |
| 223 | +var ReactInterfaceExample = { |
| 224 | + make: Playground$ReactInterfaceExample |
| 225 | +}; |
| 226 | + |
| 227 | +function Playground$PropTypeInjectionExample(props) { |
| 228 | + var msg = props.isHuman ? "hello human" : "hello fellow computer"; |
| 229 | + return JsxRuntime.jsx("div", { |
| 230 | + children: msg |
| 231 | + }); |
| 232 | +} |
| 233 | + |
| 234 | +var PropTypeInjectionExample = { |
| 235 | + make: Playground$PropTypeInjectionExample |
| 236 | +}; |
| 237 | + |
| 238 | +function Playground$PropSpreadExample(props) { |
| 239 | + return JsxRuntime.jsx(make, { |
| 240 | + name: "World" |
| 241 | + }); |
| 242 | +} |
| 243 | + |
| 244 | +var PropSpreadExample = { |
| 245 | + make: Playground$PropSpreadExample |
| 246 | +}; |
| 247 | + |
| 248 | +var root = JsxRuntime.jsxs("div", { |
| 249 | + children: [ |
| 250 | + JsxRuntime.jsx(Playground$AutomaticModeExample, { |
| 251 | + name: "Automatic" |
| 252 | + }), |
| 253 | + JsxRuntime.jsx(Playground$ClassicModeExample, { |
| 254 | + name: "Classic" |
| 255 | + }), |
| 256 | + JsxRuntime.jsx(make, { |
| 257 | + name: "NoAttribute" |
| 258 | + }), |
| 259 | + JsxRuntime.jsx(Playground$ReactInterfaceExample, { |
| 260 | + name: "Interface" |
| 261 | + }), |
| 262 | + JsxRuntime.jsx(Playground$PropTypeInjectionExample, { |
| 263 | + isHuman: true |
| 264 | + }), |
| 265 | + JsxRuntime.jsx(Playground$PropSpreadExample, {}) |
| 266 | + ] |
| 267 | + }); |
| 268 | + |
| 269 | +export { |
| 270 | + AutomaticModeExample , |
| 271 | + ClassicModeExample , |
| 272 | + NoAttributeExample , |
| 273 | + ReactInterfaceExample , |
| 274 | + PropTypeInjectionExample , |
| 275 | + PropSpreadExample , |
| 276 | + root , |
| 277 | +} |
| 278 | +``` |
| 279 | +</CodeTab> |
| 280 | + |
| 281 | +### How to migrate to JSX v4? |
| 282 | + |
| 283 | +We provide a full [migration guide](/docs/react/latest/migrate-react) with all the details of an migration. |
| 284 | + |
| 285 | +Make sure to also check out the [rescript-react changelog](https://github.com/rescript-lang/rescript-react/blob/master/CHANGELOG.md) as well. |
| 286 | + |
| 287 | +## What's next? |
| 288 | + |
| 289 | +Our contributors are already one step ahead and are currently working on improvements for the next major v11 release. Things that are currently being explored: |
| 290 | + |
| 291 | +- Make uncurried functions the default. This will be a huge change in terms of how we do interop and will open completely new ways to interact with existing codebases. It will also allow us to improve tooling in ways that wouldn't have been possible in a curried language. |
| 292 | +- Explorations for a community "standard library" that goes beyond `Belt` and `Js.*`. This will also involve disabling / removing global "Stdlib" modules that shouldn't be used (e.g. `Array`, `List`, etc). |
| 293 | +- New tooling to generate markdown from docstrings (module, type and value level). This will be super simple, but very effective. |
| 294 | +- Explorations for a [localized documentation page](https://forum.rescript-lang.org/t/translation-project-rescript-lang-org/4022) (currently in a slowed-down exploration phase, but we will be getting there) |
| 295 | + |
| 296 | +Check out the [v11](https://github.com/rescript-lang/rescript-compiler/issues?q=is%3Aopen+is%3Aissue+milestone%3Av11.0) milestone on our `rescript-lang` repo for more details on future improvements. |
| 297 | + |
| 298 | +## Acknowledgements |
| 299 | + |
| 300 | +As always, we want to thank our [contributors](https://github.com/rescript-lang/rescript-compiler/graphs/contributors?from=2019-11-24&to=2023-02-02&type=c) for building an amazing platform. Special thanks go out to [mununki](https://github.com/mununki) for building the new JSX v4 syntax. Amazing work! |
| 301 | + |
| 302 | +## That's it |
| 303 | + |
| 304 | +We hope you enjoy the newest improvements as much as we do. |
| 305 | + |
| 306 | +In case there's any issues / problems, make sure to report bugs to [rescript-lang/rescript-compiler](https://github.com/rescript-lang/rescript-compiler) (language / syntax / jsx), [rescript-lang/rescript-react](https://github.com/rescript-lang/rescript-react) (React 16 / 18 binding) or [rescript-association/rescript-lang.org](https://github.com/rescript-association/rescript-lang.org) (documentation) repositories. |
| 307 | + |
| 308 | +Also feel free to visit the [ReScript forum](https://forum.rescript-lang.org/) to ask questions and connect with other ReScripters. |
0 commit comments