├── .gitignore ├── README.md ├── step1 ├── .gitignore ├── README.md ├── run.sh └── src │ └── test.re ├── step2 ├── .gitignore ├── README.md ├── run.sh └── src │ ├── myDep.re │ └── test.re ├── step3 ├── .gitignore ├── README.md ├── Staleness.md ├── run.sh └── src │ ├── myDep.re │ ├── myDep2.re │ └── test.re ├── step4 ├── .gitignore ├── .merlin ├── README.md ├── run.sh └── src │ ├── myDep.re │ ├── myDep2.re │ └── test.re ├── step5 ├── .gitignore ├── .merlin ├── README.md ├── run.sh └── src │ ├── myDep.re │ ├── myDep2.re │ └── test.re ├── step6 ├── .gitignore ├── .merlin ├── README.md ├── node_modules │ └── muffin │ │ ├── muffin.re │ │ └── secret.re ├── run.sh └── src │ ├── myDep.re │ ├── myDep2.re │ └── test.re └── step7 ├── .gitignore ├── .merlin ├── README.md ├── bsconfig.json ├── package.json ├── run.sh └── src ├── myDep.re ├── myDep2.re └── test.re /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro to Reason Compilation 2 | 3 | [Reason](http://reasonml.github.io/) is a syntax and tooling ecosystem for OCaml. The OCaml's compilation and build system can get rather sophisticated; This repository serves as a tutorial that explains some of the intricacies. It's always nice to know what really happens under the hood! 4 | 5 | **Note** that this is for the curious people. You don't need to know these things immediately when you start consuming Reason and the rest. If you just want to try Reason, [this](https://reasonml.github.io/guide/javascript) is the right place to start instead. 6 | 7 | We start with the most basic OCaml compiler commands and gradually build up to a productive workflow. Each step resides in its independent, fully functional folder. 8 | 9 | If you feel certain steps aren't clear, or if you'd like to request a new step, feel free to submit an issue or a pull request! P.S. we're on IRC freenode #reasonml and [Discord reasonml](https://discord.gg/reasonml). 10 | 11 | ## Requirement 12 | 13 | Have Reason >=1.13.5 installed. See [this help page](https://reasonml.github.io/guide/editor-setup) 14 | -------------------------------------------------------------------------------- /step1/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | out 3 | 4 | # Files such as `foo.cmi` and `bar.cmo` are compiled artifacts. You don't have 5 | # to know what they do at the moment; if you're curious, check 6 | # https://github.com/facebook/reason/wiki/OCaml-Ecosystem-Extensions-List 7 | 8 | *.cm* 9 | -------------------------------------------------------------------------------- /step1/README.md: -------------------------------------------------------------------------------- 1 | # 1 - Compile and Run a Single Reason File 2 | 3 | OCaml's compiler has a nice, flexible pre-processing step described in `run.sh`. A Reason file is just an ordinary OCaml file that leverages that feature. Therefore, everything that semantically works in OCaml should, in theory, work in Reason, and vice-versa. 4 | 5 | We start with a shell file, `run.sh`, which, upon execution, compiles & runs the output. See also the .gitignore. 6 | -------------------------------------------------------------------------------- /step1/run.sh: -------------------------------------------------------------------------------- 1 | # `ocamlc` is the OCaml bytecode compiler. You can optionally compile to a 2 | # platform-dependent, but more optimized output (native code) with `ocamlopt`, 3 | # whose usage is similar; we'll defer this to later. 4 | 5 | # Explanations of compiler flags (when in doubt, there's always `ocamlc --help`) 6 | 7 | # -pp: before processing the source file, pass it through a preprocessor. This 8 | # is a powerful feature that's at the heart of how the Reason syntax 9 | # transform works. We're basically taking a raw text file and piping it 10 | # through our custom lexer & parser, before handling over the valid OCaml 11 | # abstract syntax tree for actual compilation. For Reason's `refmt` 12 | # command, `refmt --print binary` prints the binary AST. 13 | # -o: output file name. 14 | # -impl: `ocamlc` recognizes, by default, `ml` files (and `mli`, which we'll 15 | # talk about later). Reason uses new file extensions, `re` (and `rei`). 16 | # In order to make `ocamlc` understand that `.re` is a normal source file 17 | # equivalent to a `.ml`, we pass the `impl` flag, meaning "this is an 18 | # implementation file" (as opposed to an interface file, `mli/rei`). 19 | ocamlc -pp "refmt --print binary" -o ./out -impl src/test.re 20 | 21 | # Run! 22 | ./out 23 | -------------------------------------------------------------------------------- /step1/src/test.re: -------------------------------------------------------------------------------- 1 | print_endline "hi"; 2 | -------------------------------------------------------------------------------- /step2/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | out 3 | 4 | # Files such as `foo.cmi` and `bar.cmo` are compiled artifacts. You don't have 5 | # to know what they do at the moment; if you're curious, check 6 | # https://github.com/facebook/reason/wiki/OCaml-Ecosystem-Extensions-List 7 | 8 | *.cm* 9 | -------------------------------------------------------------------------------- /step2/README.md: -------------------------------------------------------------------------------- 1 | # 2 - Compile Two Files, Where One Depends on Another 2 | 3 | Now we're introducing a new, internal dependency. See the additions in `run.sh`! 4 | -------------------------------------------------------------------------------- /step2/run.sh: -------------------------------------------------------------------------------- 1 | # Explanations of compiler flags (when in doubt, there's always `ocamlc --help`) 2 | 3 | # -pp: before processing the source file, pass it through a preprocessor. This 4 | # is a powerful feature that's at the heart of how the Reason syntax 5 | # transform works. We're basically taking a raw text file and piping it 6 | # through our custom lexer & parser, before handling over the valid OCaml 7 | # abstract syntax tree for actual compilation. For Reason's `refmt` 8 | # command, `refmt --print binary` prints the binary AST. 9 | # -o: output file name. 10 | # -impl: `ocamlc` recognizes, by default, `ml` files (and `mli`, which we'll 11 | # talk about later). Reason uses new file extensions, `re` (and `rei`). 12 | # In order to make `ocamlc` understand that `.re` is a normal source file 13 | # equivalent to a `.ml`, we pass the `impl` flag, meaning "this is an 14 | # implementation file" (as opposed to an interface file, `mli/rei`). 15 | # -I: "search in that directory for dependencies". You may wonder why this is 16 | # necessary, given that we've already passed both files to the compiler. 17 | # Doesn't it already know where the sources are? It doesn't. In reality, 18 | # we're really just compiling two files independently, one after another, 19 | # in the specified order. You can imagine a parallelized build system which 20 | # invokes two separate `ocamlc` commands, one for each `.re` respectively. 21 | # In this case, the compiler wouldn't know about these source files since 22 | # they're not compiled together anymore. 23 | # The order of compilation is important! if you place `-impl src/test.re` 24 | # before `-impl src/myDep.re`, you'll get an error saying "Reference to 25 | # undefined global `MyDep'". `myDep.re` has to be compiled first. We're 26 | # effectively manually sorting the dependency graph (a topological sort) 27 | # right now. We'll change that soon. 28 | 29 | # Example of wrong compilation order: 30 | # ocamlc -pp refmt -o out -I src/ -impl src/test.re -impl src/myDep.re 31 | ocamlc -pp "refmt --print binary" -o ./out -I src/ -impl src/myDep.re -impl src/test.re 32 | 33 | # Run! 34 | ./out 35 | -------------------------------------------------------------------------------- /step2/src/myDep.re: -------------------------------------------------------------------------------- 1 | let secret = "hello"; 2 | -------------------------------------------------------------------------------- /step2/src/test.re: -------------------------------------------------------------------------------- 1 | /* Note that `myDep.re` becomes the module `MyDep`. In OCaml and Reason syntax, a module is upper-cased. */ 2 | print_endline MyDep.secret; 3 | -------------------------------------------------------------------------------- /step3/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | out 3 | 4 | # Files such as `foo.cmi` and `bar.cmo` are compiled artifacts. You don't have 5 | # to know what they do at the moment; if you're curious, check 6 | # https://github.com/facebook/reason/wiki/OCaml-Ecosystem-Extensions-List 7 | 8 | *.cm* 9 | -------------------------------------------------------------------------------- /step3/README.md: -------------------------------------------------------------------------------- 1 | # 3 - Compile Many Files, Automatically, in the Right Order 2 | 3 | We can't manually sort the source files each time there's a dependency change. Let's automate it! This section also comes with extra info on a common language & build system compilation mistake in Staleness.md. Read `run.sh` first though. 4 | -------------------------------------------------------------------------------- /step3/Staleness.md: -------------------------------------------------------------------------------- 1 | # For Your Curiosity Only 2 | 3 | Skip if you aren't interested in this! 4 | 5 | Here's a typical pitfall scenario that happens when the artifacts aren't cleaned correctly. This happens to lots of projects, in most programming languages & build tools. 6 | 7 | See the artifacts in `src/`? `myDep.cmo`, and all. Do the following: 8 | 9 | ```sh 10 | rm -f src/*.cm* 11 | ocamlc -pp refmt -o ./out -I src/ -impl src/myDep2.re -impl src/myDep.re -impl src/test.re 12 | ``` 13 | 14 | You'll get the error message: 15 | 16 | ``` 17 | File "src/myDep2.re", line 1, characters 13-25: 18 | Error: Unbound module MyDep 19 | ``` 20 | 21 | If you've read the comments in `run.sh`, you'd know why this happens. We've compiled `myDep.re` and `myDep2.re` in the wrong order! Now, re-compile in the right order: 22 | 23 | ```sh 24 | rm -f src/*.cm* 25 | ocamlc -pp refmt -o out -I src/ -impl src/myDep.re -impl src/myDep2.re -impl src/test.re 26 | ``` 27 | 28 | Everything fine. Now, *without* cleaning the stale artifacts, recompile in the wrong order! 29 | 30 | ```sh 31 | ocamlc -pp refmt -o ./out -I src/ -impl src/myDep2.re -impl src/myDep.re -impl src/test.re 32 | ``` 33 | 34 | Output: 35 | 36 | ``` 37 | Error: Error while linking src/myDep2.cmo: 38 | Reference to undefined global `MyDep' 39 | ``` 40 | 41 | Why is the error different now? 42 | 43 | `ocamlc` has a compilation step, and a linking step, roughly speaking. Until now, we've used some `ocamlc` shorthands to do both in one shot. 44 | 45 | The compilation step takes each file and compiles. *TODO: explain why this happens*. 46 | 47 | Moral of the story: use a good build system ([*cough*](http://bucklescript.github.io/bucklescript/Manual.html#_bucklescript_build_system_code_bsb_code)) or debug the dependency graph yourself... =) 48 | -------------------------------------------------------------------------------- /step3/run.sh: -------------------------------------------------------------------------------- 1 | # Before automate the compile step dependencies ordering, let's clean up the 2 | # artifacts. We're naively cleaning all the artifacts before every compilation 3 | # to make sure that, when the compilation order changes because of a dependency 4 | # graph change, we don't pick up the stale, existing artifacts. If you're 5 | # interested in knowing more, see Staleness.md for more info. 6 | 7 | rm -f src/*.cm* 8 | 9 | # Introducing `ocamldep`! It's a utility that ships with OCaml, which analyses 10 | # the files and outputs them in the order of dependency (aka, a topological 11 | # sort). If A depends on B then A will certainly come after B. 12 | 13 | # -pp: same flag as `ocamlc`'s. 14 | # -sort: among many functionalities of `ocamldep`, use the sorting feature. 15 | # -ml-synonym: akin to `ocamlc`'s `-impl`. "If your source files don't end with 16 | # `ml`, tell me what they end with." 17 | 18 | # shellscript-fu time! 19 | sortedFiles=$(ocamldep -pp "refmt --print binary" -sort -ml-synonym .re src/*.re) 20 | # should give: `src/myDep.re src/myDep2.re src/test.re` 21 | argsForOcaml=$(echo "$sortedFiles" | sed "s/src\//-impl src\//g") 22 | # should give: `-impl src/myDep.re -impl src/myDep2.re -impl src/test.re` 23 | 24 | # The flags are the same ones used in step 2. 25 | ocamlc -pp "refmt --print binary" -o ./out -I src/ $argsForOcaml 26 | 27 | # Run! 28 | ./out 29 | -------------------------------------------------------------------------------- /step3/src/myDep.re: -------------------------------------------------------------------------------- 1 | let secret = "hello"; 2 | -------------------------------------------------------------------------------- /step3/src/myDep2.re: -------------------------------------------------------------------------------- 1 | let secret = MyDep.secret ^ " world"; 2 | -------------------------------------------------------------------------------- /step3/src/test.re: -------------------------------------------------------------------------------- 1 | /* Note that `myDep.re` becomes the module `MyDep`. In OCaml and Reason syntax, a module is upper-cased. */ 2 | 3 | /* We now have 2 dependencies, where MyDep2 also depends on myDep. So the compilation order should be [myDep, 4 | myDep2, test] */ 5 | print_endline MyDep.secret; 6 | print_endline MyDep2.secret; 7 | -------------------------------------------------------------------------------- /step4/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | out 3 | 4 | # Files such as `foo.cmi` and `bar.cmo` are compiled artifacts. You don't have 5 | # to know what they do at the moment; if you're curious, check 6 | # https://github.com/facebook/reason/wiki/OCaml-Ecosystem-Extensions-List 7 | 8 | *.cm* 9 | -------------------------------------------------------------------------------- /step4/.merlin: -------------------------------------------------------------------------------- 1 | # Setup guide: 2 | # https://github.com/the-lambda-church/merlin/wiki/project-configuration 3 | 4 | # B for built artifacts. In our case they reside alongside the source folder 5 | B src 6 | 7 | # S for source 8 | S src 9 | 10 | # Merlin picks up the artifacts of the compilation, so **don't forget** to 11 | # compile your project at least once (through `run.sh` naturally) in order to 12 | # have these artifacts in the first place! 13 | 14 | # **Note**: this also means that, when you add a new file, you need to compile 15 | # it once to generates the new artifacts for Merlin. Practically, if you have a 16 | # watcher installed, it'll only take a few seconds top for the change to 17 | # "propagate" to your editor's plugin. 18 | -------------------------------------------------------------------------------- /step4/README.md: -------------------------------------------------------------------------------- 1 | # 4 - Small Break; Add Miscellaneous Files 2 | 3 | Check point! This step will set up some common tools you'd use during Reason's development. 4 | 5 | - [Merlin](https://github.com/the-lambda-church/merlin): check out the description [here](https://github.com/the-lambda-church/merlin/wiki/project-configuration). We're writing a small `.merlin` file in this step. Read through the comments in the file; they're helpful. 6 | 7 | - Reason has [editor support](https://reasonml.github.io/guide/tools#editor-integration) for Atom, Vim, Emacs, etc. using Merlin. They're very nice-to-haves. 8 | 9 | - Ultimately, Reason will provide you an opinionated (but optional) build system that takes care of generating everything you're seeing here & more. Our goal for the opinionated build system is to: 10 | - Enable a hassle-free onboarding experience, so that people don't have to fit the whole ecosystem & tooling & workflow in their head before starting their first `.re` file. 11 | - Enable nice features that are otherwise hard to have in an agnostic build & tooling ecosystem. For example, if your directory structure abides by some format we prefer, then we can make the documentation, repl bootstrapping, merlin and the rest work together. 12 | -------------------------------------------------------------------------------- /step4/run.sh: -------------------------------------------------------------------------------- 1 | # See Step 3 for more info on these commands 2 | 3 | # clean 4 | rm -f src/*.cm* 5 | 6 | # shellscript-fu time! 7 | sortedFiles=$(ocamldep -pp "refmt --print binary" -sort -ml-synonym .re src/*.re) 8 | # should give: `src/myDep.re src/myDep2.re src/test.re` 9 | argsForOcaml=$(echo "$sortedFiles" | sed "s/src\//-impl src\//g") 10 | # should give: `-impl src/myDep.re -impl src/myDep2.re -impl src/test.re` 11 | 12 | # The flags are the same ones used in step 2, plus: 13 | 14 | # -g: enables stack trace, aka always, always have this flag turned on. Why this 15 | # flag isn't included by default is due to historical & slight performance 16 | # reasons (which don't matter anymore. Come on, stack traces!). 17 | # -bin-annot: again, see 18 | # github.com/facebook/reason/wiki/OCaml-Ecosystem-Extensions-List. 19 | # This flag generates the .cmt files with the type info tools like 20 | # Merlin can use to provide you e.g. autocomplete and 21 | # jump-to-definition. 22 | 23 | ocamlc -g -bin-annot -pp "refmt --print binary" -o ./out -I src/ $argsForOcaml 24 | 25 | # Run! 26 | ./out 27 | -------------------------------------------------------------------------------- /step4/src/myDep.re: -------------------------------------------------------------------------------- 1 | let secret = "hello"; 2 | -------------------------------------------------------------------------------- /step4/src/myDep2.re: -------------------------------------------------------------------------------- 1 | let secret = MyDep.secret ^ " world"; 2 | -------------------------------------------------------------------------------- /step4/src/test.re: -------------------------------------------------------------------------------- 1 | print_endline MyDep.secret; 2 | 3 | print_endline MyDep2.secret; 4 | /* ^ Try typing `MyDep.s` */ 5 | /* It should have auto-completed `secret`. If it doesn't, make sure you've reloaded your editor? */ 6 | /* If you're using atom-reason, go to the plugin's setting page and put in the correct paths. */ 7 | /* TODO: describe this better. */ 8 | -------------------------------------------------------------------------------- /step5/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # See run.sh. This is our new output directory. 4 | _build/ 5 | -------------------------------------------------------------------------------- /step5/.merlin: -------------------------------------------------------------------------------- 1 | # Setup guide: 2 | # https://github.com/the-lambda-church/merlin/wiki/project-configuration 3 | 4 | # B for built artifacts. 5 | B _build/self 6 | 7 | # S for source 8 | S src 9 | 10 | # Merlin picks up the artifacts of the compilation, so **don't forget** to 11 | # compile your project at least once (through `run.sh` naturally) in order to 12 | # have these artifacts in the first place! 13 | 14 | # **Note**: this also means that, when you add a new file, you need to compile 15 | # it once to generates the new artifacts for Merlin. Practically, if you have a 16 | # watcher installed, it'll only take a few seconds top for the change to 17 | # "propagate" to your editor's plugin. 18 | -------------------------------------------------------------------------------- /step5/README.md: -------------------------------------------------------------------------------- 1 | # 5 - Preparation for Third-Party Dependencies 2 | 3 | In preparation for the next step (third-party dependencies!), it'd be nice to 4 | have a unified folder for the built artifacts, so that we: 5 | - don't pollute third-party with intermediate compilation artifacts alongside 6 | their source code 7 | - can easily remove all artifacts by deleting a single folder 8 | 9 | Additionally, we'll expand the naive compilation in `run.sh` into something slightly more sophisticated, and make the corresponding changes in `.merlin`. 10 | -------------------------------------------------------------------------------- /step5/run.sh: -------------------------------------------------------------------------------- 1 | # Move all tbe artifacts into _build/, so that we can have an emergency 2 | # "wipe-the-world" clean 3 | rm -rf _build 4 | 5 | mkdir -p _build/self 6 | 7 | # Time to split the `ocamlc` we've had so far into distinct steps. In reality, 8 | # that single convenience `ocamlc` command is (roughly) composed of compiling, 9 | # then linking. Compiling turns a source into an artifact, while checking that 10 | # everything type-checks and that the dependencies are satisfied. Linking 11 | # bundles the files together into a final executable. 12 | 13 | # 1. Compiling 14 | 15 | # Compiling each file separately has many future benefits. First, we can 16 | # parallelize the compilation for better speed. Second, (thanks to OCaml's great 17 | # module system that forms the compilation boundaries), we can safely skip 18 | # recompiling sources whose dependencies's type signatures haven't changed! 19 | # Heck, you can swap out entire implementations without recompiling dependents. 20 | # Feel free to explore this vast domain! One link to get you started: 21 | # stackoverflow.com/questions/9843378/ocaml-module-types-and-separate-compilation 22 | 23 | selfSortedFiles=$(ocamldep -pp "refmt --print binary" -sort -ml-synonym .re src/*.re) 24 | # should give: src/myDep.re src/myDep2.re src/test.re 25 | for source in $selfSortedFiles 26 | do 27 | destination=$(echo $source | sed "s/src/_build\/self/" | sed "s/\.re$//") 28 | # should give: _build/self/myDep then _build/self/myDep2 then _build/self/test 29 | # The only new flag here is `-c`, which says "compile, but don't link". 30 | ocamlc -g -bin-annot -pp "refmt --print binary" -o $destination -I _build/self -c -impl $source 31 | done 32 | 33 | # 2. Linking. 34 | 35 | # We can drop the flags -pp, -impl, -bin-annot, etc. Linking doesn't need them. 36 | # What we need are the `cmo` files, in the topological order (again, see 37 | # github.com/facebook/reason/wiki/OCaml-Ecosystem-Extensions-List#the-list). 38 | selfSortedArtifacts=$(echo $selfSortedFiles | sed "s/src/_build\/self/g" | sed "s/\.re/\.cmo/g") 39 | # should give: _build/self/myDep.cmo _build/self/myDep2.cmo _build/self/test.cmo 40 | ocamlc -o _build/out -I _build/self $selfSortedArtifacts 41 | 42 | # Knowing the right compiling & linking command might be tricky and initially 43 | # frustrating (if you ever link stuff manually for whatever reason, instead of 44 | # using a proper build system). If you run into trouble, don't forget that we're 45 | # always in IRC freenode #reasonml and gitter.im/facebook/reason 46 | 47 | # Run! 48 | ./_build/out 49 | -------------------------------------------------------------------------------- /step5/src/myDep.re: -------------------------------------------------------------------------------- 1 | let secret = "hello"; 2 | -------------------------------------------------------------------------------- /step5/src/myDep2.re: -------------------------------------------------------------------------------- 1 | let secret = MyDep.secret ^ " world"; 2 | -------------------------------------------------------------------------------- /step5/src/test.re: -------------------------------------------------------------------------------- 1 | print_endline MyDep.secret; 2 | 3 | print_endline MyDep2.secret; 4 | -------------------------------------------------------------------------------- /step6/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | _build/ 4 | -------------------------------------------------------------------------------- /step6/.merlin: -------------------------------------------------------------------------------- 1 | # Setup guide: 2 | # https://github.com/the-lambda-church/merlin/wiki/project-configuration 3 | 4 | # B for built artifacts. 5 | B _build/self 6 | B _build/muffin 7 | 8 | # S for source 9 | S src 10 | S node_modules/muffin 11 | 12 | # Merlin picks up the artifacts of the compilation, so **don't forget** to 13 | # compile your project at least once (through `run.sh` naturally) in order to 14 | # have these artifacts in the first place! 15 | 16 | # **Note**: this also means that, when you add a new file, you need to compile 17 | # it once to generates the new artifacts for Merlin. Practically, if you have a 18 | # watcher installed, it'll only take a few seconds top for the change to 19 | # "propagate" to your editor's plugin. 20 | -------------------------------------------------------------------------------- /step6/README.md: -------------------------------------------------------------------------------- 1 | # 6 - Add a Third-Party Dependency 2 | 3 | Thanks to our previous step, adding a new dependency is easy. 4 | 5 | The OCaml compiler is package manager-agnostic. [OPAM](https://opam.ocaml.org) is the common one used by the community. Though if you're reading this tutorial, you likely know how to use [npm](https://www.npmjs.com). Good news: we support that as well. This tutorial will focus on the latter. 6 | 7 | Usually we'd write a package.json here and let you do `npm install` to install all the dependencies into `./node_modules`; for the sake of simplicity, we're gonna hardcode a dependency (check it out, it's only two files). You get the idea. 8 | 9 | We'll also make the corresponding additions to `.merlin`. 10 | -------------------------------------------------------------------------------- /step6/node_modules/muffin/muffin.re: -------------------------------------------------------------------------------- 1 | let secretFlavor = Secret.flavor; 2 | -------------------------------------------------------------------------------- /step6/node_modules/muffin/secret.re: -------------------------------------------------------------------------------- 1 | let flavor = "strawberry"; 2 | -------------------------------------------------------------------------------- /step6/run.sh: -------------------------------------------------------------------------------- 1 | # See previous step for the explanations of compiling & linking. 2 | 3 | # Clean 4 | rm -rf _build 5 | 6 | # First, build the third-party dependency! Notice that This is mostly the same 7 | # as building our first-party code. We could have deduped most of this logic 8 | # too. 9 | mkdir -p _build/muffin 10 | muffinSortedFiles=$(ocamldep -pp "refmt --print binary" -sort -ml-synonym .re node_modules/muffin/*.re) 11 | # should give: node_modules/muffin/secret.re node_modules/muffin/muffin.re 12 | for source in $muffinSortedFiles 13 | do 14 | destination=$(echo $source | sed "s/node_modules/_build/" | sed "s/\.re$//") 15 | # should give: _build/muffin/secret then _build/muffin/muffin 16 | ocamlc -g -bin-annot -pp "refmt --print binary" -o $destination -I _build/muffin -c -impl $source 17 | done 18 | 19 | # Now build ourselves. Same as previous section 20 | mkdir -p _build/self 21 | selfSortedFiles=$(ocamldep -pp "refmt --print binary" -sort -ml-synonym .re src/*.re) 22 | # should give: src/myDep.re src/myDep2.re src/test.re 23 | for source in $selfSortedFiles 24 | do 25 | destination=$(echo $source | sed "s/src/_build\/self/" | sed "s/\.re$//") 26 | # should give: _build/self/myDep then _build/self/myDep2 then _build/self/test 27 | ocamlc -g -bin-annot -pp "refmt --print binary" -o $destination -I _build/muffin \ 28 | -I _build/self -c -impl $source 29 | done 30 | 31 | # Link. Note that we need to pass *all* the cmos in order, including third-party 32 | # ones. This goes against good abstractions and becomes burdensome for many 33 | # dependencies; the solution is library files (.cma), which, for the sake of 34 | # brevity, we won't talk about here. 35 | muffinSortedArtifacts=$(echo $muffinSortedFiles | sed "s/node_modules/_build/g" | sed "s/\.re/\.cmo/g") 36 | # should give: _build/muffin/secret.cmo _build/muffin/muffin.cmo 37 | selfSortedArtifacts=$(echo $selfSortedFiles | sed "s/src/_build\/self/g" | sed "s/\.re/\.cmo/g") 38 | # should give: _build/self/myDep.cmo _build/self/myDep2.cmo _build/self/test.cmo 39 | ocamlc -o _build/out -I _build/muffin/ -I _build/self $muffinSortedArtifacts $selfSortedArtifacts 40 | 41 | # Run. Modify node_modules/muffin/secret.re and see your changes reflected! 42 | ./_build/out 43 | -------------------------------------------------------------------------------- /step6/src/myDep.re: -------------------------------------------------------------------------------- 1 | let secret = "hello"; 2 | -------------------------------------------------------------------------------- /step6/src/myDep2.re: -------------------------------------------------------------------------------- 1 | let secret = MyDep.secret ^ " world"; 2 | -------------------------------------------------------------------------------- /step6/src/test.re: -------------------------------------------------------------------------------- 1 | print_endline MyDep.secret; 2 | 3 | print_endline MyDep2.secret; 4 | 5 | print_endline Muffin.secretFlavor; 6 | -------------------------------------------------------------------------------- /step7/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | _build/ 4 | 5 | # New item. Ignore our dependencies that we can `npm install` 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /step7/.merlin: -------------------------------------------------------------------------------- 1 | # Setup guide: 2 | # https://github.com/the-lambda-church/merlin/wiki/project-configuration 3 | 4 | # B for built artifacts. 5 | B _build/self 6 | B _build/reason-js 7 | 8 | # BuckleScript's artifacts are installed upon package installation; they resides 9 | # here. Merlin's jump-to-location can transport you to these locations! 10 | B node_modules/bs-platform/lib/ocaml 11 | 12 | # S for source 13 | S src 14 | S node_modules/reason-js/src 15 | 16 | # TODO: put BuckleScript ppx here too 17 | 18 | # Merlin picks up the artifacts of the compilation, so **don't forget** to 19 | # compile your project at least once (through `run.sh` naturally) in order to 20 | # have these artifacts in the first place! 21 | 22 | # **Note**: this also means that, when you add a new file, you need to compile 23 | # it once to generates the new artifacts for Merlin. Practically, if you have a 24 | # watcher installed, it'll only take a few seconds top for the change to 25 | # "propagate" to your editor's plugin. 26 | -------------------------------------------------------------------------------- /step7/README.md: -------------------------------------------------------------------------------- 1 | # 7 - Compile to JavaScript 2 | 3 | Would you like some sweet, sweet interop with the JS ecosystem? 4 | 5 | We're gonna use [BuckleScript](https://github.com/bloomberg/bucklescript), an OCaml-to-JS compiler that emits [really](https://github.com/bloomberg/bucklescript#output), [really](https://github.com/bloomberg/bucklescript-addons) clean output. 6 | 7 | **Note** that BuckleScript comes with a great, one-click build system already, [bsb](http://bucklescript.github.io/bucklescript/Manual.html#_bucklescript_build_system_code_bsb_code). Again, this guide is just for the curious folks who'd like to know how things work under the hood. 8 | 9 | We're gonna use real third-party dependencies this time around. Check the new package.json's `dependencies` field (a reason binding to JS functions) and `devDependencies` field (BuckleScript compiler). `npm install` in the current directory to get them. 10 | 11 | Making this work is simple. See the updated `run.sh`. We've also made modifications to `.gitignore` and `.merlin`. 12 | 13 | **Make sure you read the compiled output**. Yes, read it. Trust us on this one! 14 | -------------------------------------------------------------------------------- /step7/bsconfig.json: -------------------------------------------------------------------------------- 1 | /* This is the BuckleScript configuration file. Note that this is a comment; 2 | BuckleScript comes with a JSON parser that supports comments and trailing 3 | comma. If this screws with your editor highlighting, please tell us by filing 4 | an issue! */ 5 | 6 | /* BuckleScript uses bsconfig as a compilation config file, but also as an 7 | anchoring point to know where the root of the project is. Since we're learning 8 | the fundamentals, we'll only be using bsconfig for the latter purpose. For the 9 | former purpose, see 10 | http://bucklescript.github.io/bucklescript/Manual.html#_bucklescript_build_system_code_bsb_code */ 11 | 12 | { 13 | "name" : "step7", 14 | } 15 | -------------------------------------------------------------------------------- /step7/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step7", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "src/test.re", 6 | "dependencies": { 7 | "reason-js": "0.0.15" 8 | }, 9 | "devDependencies": { 10 | "bs-platform": "^1.7.5" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "author": "", 16 | "private": true, 17 | "license": "ISC" 18 | } 19 | -------------------------------------------------------------------------------- /step7/run.sh: -------------------------------------------------------------------------------- 1 | # Don't forget to run `npm install` beforehand! 2 | 3 | # See previous step for the explanations of compiling & linking. Most of these 4 | # lines are the same. 5 | 6 | # Clean 7 | rm -rf _build 8 | 9 | # Our third-party dependency has a single file. For the sake of simplicity, 10 | # we'll remove the previous step's looping logic. For reference, we still 11 | # have the same loop logic below for our own files anyway. 12 | mkdir -p _build/reason-js 13 | # Notice that we've switched away `ocamlc` for `bsc` which works similarly to 14 | # the former. There are a few extra flags here; the BS documentation explains 15 | # them well: bucklescript.github.io/bucklescript/Manual.html#__bs_package_name 16 | 17 | 18 | 19 | # Note: we're using the package name "self" because from our point of view, 20 | # node_modules is really just another folder filled with files. This produces 21 | # the right `require` calls in the output. Again, see the BS docs and feel free 22 | # to fiddle with these options! 23 | node_modules/bs-platform/bin/bsc.exe -g -bin-annot -pp "refmt --print binary" -bs-package-name self \ 24 | -bs-package-output commonjs:_build/reason-js -o _build/reason-js/ReasonJs \ 25 | -c -impl node_modules/reason-js/src/reasonJs.re 26 | 27 | # Now build ourselves. Same as previous step. 28 | mkdir -p _build/self 29 | # We're adding the `-ppx` flag here (the rest is the same), because BuckleScript 30 | # uses some ppx preprocessors (basically, macros with special treatment). 31 | selfSortedFiles=$(ocamldep -ppx ./node_modules/bs-platform/bin/bsppx.exe -pp "refmt --print binary" -sort -ml-synonym .re src/*.re) 32 | # should give: src/myDep.re src/myDep2.re src/test.re 33 | # The flag -bs-files in `bsc` sorts the sources & compiles them, allowing us to 34 | # avoid `ocamldep` in the future. It currently doesn't work with Reason files: 35 | # https://github.com/bloomberg/bucklescript/issues/549 36 | 37 | for source in $selfSortedFiles 38 | do 39 | destination=$(echo $source | sed "s/src/_build\/self/" | sed "s/\.re$//") 40 | # should give: _build/self/myDep then _build/self/myDep2 then _build/self/test 41 | node_modules/bs-platform/bin/bsc.exe -g -bin-annot -pp "refmt --print binary" -bs-package-name self \ 42 | -bs-package-output commonjs:_build/self -I _build/self -I _build/reason-js \ 43 | -o $destination -c -impl $source 44 | done 45 | 46 | # no linking! BuckleScript maps 1 Reason/OCaml file to 1 JS file. Feel free to 47 | # use your current JS module bundler (e.g. Browserify, Webpack)! 48 | 49 | # Are you ready to run your JS output? 50 | node ./_build/self/test.js 51 | -------------------------------------------------------------------------------- /step7/src/myDep.re: -------------------------------------------------------------------------------- 1 | let secret = "hello"; 2 | -------------------------------------------------------------------------------- /step7/src/myDep2.re: -------------------------------------------------------------------------------- 1 | let secret = MyDep.secret ^ " world"; 2 | -------------------------------------------------------------------------------- /step7/src/test.re: -------------------------------------------------------------------------------- 1 | print_endline MyDep.secret; 2 | 3 | print_endline MyDep2.secret; 4 | 5 | /* values that can potentially be null are type checked correctly! */ 6 | Random.self_init (); 7 | let msg = 8 | Random.int 2 === 0 ? Js.null : Js.Null.return "This message might not display."; 9 | 10 | switch (Js.Null.to_opt msg) { 11 | | None => () 12 | | Some msg => print_endline msg 13 | }; 14 | 15 | /* let's try some BuckleScript-JavaScript interop */ 16 | ReasonJs.setTimeout (fun () => print_endline "Here's a message after a 1 second timeout.") 1000; 17 | --------------------------------------------------------------------------------