Recently I’ve started using Bucklescript (and Reason) over Elm for various reasons (Something I might blog about later). In this post I’m exploring integrating Bucklescript with Typescript.
Let’s start with a blank typescript project with webpack
You can find the inital commit of a Typescript project here.
There is nothing special in this commit, some npm dependencies: source-map-loader, ts-loader, typescript, webpack, webpack-dev-server
. The ts-config.json file is generated with tsc --init
, nothing is changed.
In the webpack.config.js
I have one entry file ./src/index.ts
that get’s compiled to ./dist
.
The index.html is just a bare html file that loads that compiled javascript. So if you run npm start
and open your browser on the url that webpack-dev-server
hosts, you should see Hello from Typescript in your console.
The first step was really easy, let’s see if the next step is any harder?
Integrating Bucklescript and Reason
Let’s first install bs-loader
and bs-platform
locally. bs-platform
contains the bucklescript compiler.
To work with bucklescript we need a bsconfig.json
file:
{
"name": "typescript-with-bucklescript",
"version": "0.1.0",
"sources": [
"src"
],
"package-specs": ["es6"],
"bsc-flags": [ "-bs-super-errors -no-alias-deps", "-color", "always"]
}
Nothing to special in here, we look for our source files in src
, compile to es6
and enable some compiler flags.
In webpack.config.js
we add the bs-loader
and resolve .ml
and .re
files:
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -14,7 +14,7 @@ module.exports = {
publicPath: '/dist/',
},
resolve: {
- extensions: ['.js', '.ts'],
+ extensions: ['.js', '.ts', '.ml', '.re'],
},
module: {
rules: [
@@ -30,6 +30,15 @@ module.exports = {
loaders: ['ts-loader'],
exclude: /node_modules/
},
+ {
+ test: /\.(re|ml)$/,
+ use: {
+ loader: 'bs-loader',
+ options: {
+ module: 'es6',
+ }
+ }
+ },
]
}
};
\ No newline at end of file
Now we are able to create 2 files: src/Bucklescript.ml
:
let hello name =
Js.log {j|Hi $(name), from Bucklescript.|j}
and src/Reason.re
:
let hello name => Js.log {j|Hi $(name), from Reason.|j};
both of which can be called from index.ts
:
const helloBucklescript = require('./Bucklescript').hello;
const helloReason = require('./Reason').hello;
helloBucklescript('Typescript');
helloReason('Typescript');
When you run npm start
you will get an error from typescript Cannot find name 'require'
, to solve this we need to install @types/node
Now you can run npm start
and everything should work. The full commit of this step can be found here.
You might wonder why I’m using require
to import the modules. That’s because I didn’t get import {x} from module
syntax working. Maybe I need typescript type definitions for this?
A strange bug
While I was playing with this setup I noticed something strange. It looks like the compilation phase is always one step late. If I change something in the Bucklescript file, it only get’s picked up after a next webpack build. I’m not sure why this is the case.
The easiest way to show the problem is like this:
"scripts": {
+ "clean": "bsb -clean",
+ "build": "webpack",
"start": "webpack-dev-server --inline --hot"
},
If we first run npm run clean
and then npm run build
we get the error that .../lib/es6/src/Reason.js
does not exist. If you would run npm run build
again the build would succeed because the file will be there.
Well, after some searching, it looks like this is a bug in bs-loader
so I submitted a PR. The bug only manifests itself because we load 2 files and bs-loader
won’t wait for the compiler for the second file.
After this fix everything works as intended, and you can start using Bucklescript/Reason together with Typescript.
Conclusion
Apart from 2 things:
- Why doesn’t the
import
syntax work? (I’m sure I’m missing something here…) - The bug in
bs-loader
integrating Bucklescript/Reason with Typescript was trivial and I’m planning on using it in some projects to have some real integrations.