├── .github
├── FUNDING.yml
└── workflows
│ └── node.yml
├── .gitignore
├── HISTORY.md
├── MIT-LICENSE
├── README.md
├── bsconfig.json
├── package.json
├── server
└── server.mjs
├── src
├── App.res
├── App.resi
├── ErrorPage.res
├── ErrorPage.resi
├── Footer.res
├── Footer.resi
├── Header.res
├── Header.resi
├── Home.res
├── Home.resi
├── Robots.res
├── Robots.resi
├── index.html
└── shared
│ ├── CssReset.res
│ ├── CssReset.resi
│ ├── Emotion.res
│ ├── Head.res
│ ├── Head.resi
│ ├── Link.res
│ ├── Link.resi
│ ├── Router.res
│ ├── Router.resi
│ ├── Spacer.res
│ └── Spacer.resi
├── statics
└── robots.txt
├── test
├── Home__test.res
├── Robots__test.res
└── utils
│ ├── Assert.res
│ └── ReactTest.res
├── webpack.config.js
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: ["bloodyowl"]
4 |
--------------------------------------------------------------------------------
/.github/workflows/node.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [main]
9 | pull_request:
10 | branches: [main]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [14.x]
19 |
20 | steps:
21 | - uses: actions/checkout@v2
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 |
27 | - name: Install
28 | run: yarn
29 |
30 | - name: Build
31 | run: yarn build
32 | env:
33 | NODE_ENV: production
34 |
35 | - name: Test
36 | run: yarn test
37 |
38 | - name: Bundle
39 | run: yarn bundle
40 | env:
41 | NODE_ENV: production
42 | PUBLIC_PATH: ${{ secrets.PUBLIC_PATH }}
43 |
44 | - name: Deploy
45 | uses: peaceiris/actions-gh-pages@v3
46 | # remove the following line to host your project on GitHub
47 | if: ${{ github.repository == 'bloodyowl/rescript-react-starter-kit'}}
48 | with:
49 | github_token: ${{ secrets.GITHUB_TOKEN }}
50 | publish_dir: ./build
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .DS_Store?
3 | node_modules/
4 | lib/
5 | .bsb.lock
6 | .merlin
7 | build/
8 | src/**/*.mjs
9 | test/**/*.mjs
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | ## 0.7.2
2 |
3 | Changes:
4 |
5 | - Externalize dev server features (de695c6)
6 |
7 | ## 0.7.1
8 |
9 | Changes:
10 |
11 | - Update ReScript CLI (280126b)
12 | - Improve dev server (87db62f)
13 |
14 | ## 0.7.0
15 |
16 | Changes:
17 |
18 | - Move to ReScript CLI (4d401fe)
19 |
20 | ## 0.6.0
21 |
22 | Features:
23 |
24 | - Add support for title/meta management (77b83cb)
25 |
26 | ## 0.5.0
27 |
28 | Features:
29 |
30 | - Handle publicPaths automatically (ceaff4e)
31 |
32 | ## 0.4.0
33 |
34 | Features:
35 |
36 | - Add live reload (c547412)
37 |
38 | ## 0.3.0
39 |
40 | Features:
41 |
42 | - Add useful buidling blocks like `Link` & `Spacer` (e4ec015)
43 |
44 | Changes:
45 |
46 | - Better docs (2523d24)
47 |
48 | ## 0.2.0
49 |
50 | Changes:
51 |
52 | - Auto open Belt for convenience (7207b67)
53 |
54 | ## 0.1.1
55 |
56 | Features:
57 |
58 | - Add bundle command (5328460)
59 |
60 | ## 0.1.0
61 |
62 | Initial version
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016-2018 Various Authors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ReScript React Starter Kit
2 |
3 | > An opinionated starter kit for ReScript React
4 |
5 |
6 |
7 | ## What's inside
8 |
9 | ### Familiar standard library
10 |
11 | The configuration automatically gives you [Belt](https://rescript-lang.org/docs/manual/latest/api/belt) and [ReScriptJs.Js](https://github.com/bloodyowl/rescript-js) in scope.
12 |
13 | This makes your code always default to JavaScript APIs if available, while giving you good manipulation functions for ReScript-specific types (like `Option` & `Result`)
14 |
15 | This means that by default, the following code:
16 |
17 | ```rescript
18 | let x = [1, 2, 3]
19 | ->Array.map(x => x * 2)
20 | ->Array.forEach(Console.log)
21 | ```
22 |
23 | will compile to the following JS (no additional runtime cost!):
24 |
25 | ```js
26 | [1, 2, 3]
27 | .map(function (x) {
28 | return x << 1;
29 | })
30 | .forEach(function (prim) {
31 | console.log(prim);
32 | });
33 | ```
34 |
35 | If you need a specific data-structure from Belt, you can prefix with `Belt`'s scope:
36 |
37 | ```rescript
38 | let x = Belt.Map.String.fromArray([("a", 1), ("b", 2)])
39 | ```
40 |
41 | ### Ready-to-go requests
42 |
43 | This starter kit gives you three building blocks to handle API calls from the get go.
44 |
45 | #### AsyncData
46 |
47 | [AsyncData](https://github.com/bloodyowl/rescript-asyncdata) is a great way to represent asynchronous data in React component state. It's a variant type that can be either `NotAsked`, `Loading` or `Done(payload)`, leaving no room for the errors you get when managing those in different state cells.
48 |
49 | #### Future
50 |
51 | Promises don't play really well with React's effect cancellation model, [Future](https://github.com/bloodyowl/rescript-future) gives you a performant equivalent that has built-in cancellation and leaves error management to the [Result](https://rescript-lang.org/docs/manual/latest/api/belt/result) type.
52 |
53 | #### Request
54 |
55 | [Request](https://github.com/bloodyowl/rescript-request) gives you a simple API to perform API calls in a way that's easy to store in React component state.
56 |
57 | ### Dev server
58 |
59 | Once your project grows, having the compiler output files and webpack watching it can lead to long waiting times. Here, the development server waits for BuckleScript to be ready before it triggers a compilation.
60 |
61 | The dev server supports basic **live reload**.
62 |
63 | ### Testing library
64 |
65 | With [ReScriptTest](https://github.com/bloodyowl/rescript-test), you get a light testing framework that plays nicely with React & lets you mock HTTP call responses.
66 |
67 | The assertion part is on your side, the library simply runs and renders the tests.
68 |
69 | ```rescript
70 | open ReactTest
71 |
72 | testWithReact("Robots renders", container => {
73 | let (future, resolve) = Deferred.make()
74 |
75 | let fetchRobotsTxt = () => future
76 |
77 | act(() => ReactDOM.render(
` with the metadata you like for a given route, this binds to [react-helmet](https://github.com/nfl/react-helmet).
117 |
118 | ## Getting started
119 |
120 | ```console
121 | $ yarn
122 | $ yarn start
123 | # And in a second terminal tab
124 | $ yarn server
125 | ```
126 |
127 | ## Commands
128 |
129 | ### yarn start
130 |
131 | Starts ReScript compiler in watch mode
132 |
133 | ### yarn server
134 |
135 | Starts the development server
136 |
137 | ### yarn build
138 |
139 | Builds the project
140 |
141 | ### yarn bundle
142 |
143 | Bundles the project in `build`
144 |
145 | ### yarn test
146 |
147 | Runs the test suite
148 |
--------------------------------------------------------------------------------
/bsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rescript-react-starter-kit",
3 | "reason": {
4 | "react-jsx": 3
5 | },
6 | "sources": [
7 | {
8 | "dir": "src",
9 | "subdirs": true
10 | },
11 | {
12 | "dir": "test",
13 | "subdirs": true,
14 | "type": "dev"
15 | }
16 | ],
17 | "package-specs": [
18 | {
19 | "module": "es6",
20 | "in-source": true
21 | }
22 | ],
23 | "suffix": ".mjs",
24 | "bs-dependencies": [
25 | "@rescript/react",
26 | "rescript-future",
27 | "rescript-request",
28 | "rescript-asyncdata",
29 | "@ryyppy/rescript-promise",
30 | "rescript-js"
31 | ],
32 | "bs-dev-dependencies": ["rescript-test"],
33 | "bsc-flags": ["-open Belt", "-open ReScriptJs__Js"]
34 | }
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rescript-react-starter-kit",
3 | "version": "0.7.2",
4 | "description": "A starter kit for ReScript React applications",
5 | "main": "src/App.bs.js",
6 | "repository": "git@github.com:bloodyowl/rescript-react-starter-kit.git",
7 | "author": "bloodyowl
19 |
24 | >
25 | }
26 |
--------------------------------------------------------------------------------
/src/ErrorPage.resi:
--------------------------------------------------------------------------------
1 | @react.component
2 | let make: (~text: string) => React.element
3 |
--------------------------------------------------------------------------------
/src/Footer.res:
--------------------------------------------------------------------------------
1 | module Styles = {
2 | open Emotion
3 | let container = css({
4 | "flexGrow": 0,
5 | "padding": 10,
6 | })
7 | let copyright = css({
8 | "textAlign": "center",
9 | "margin": 0,
10 | "fontSize": 14,
11 | })
12 | }
13 |
14 | @react.component
15 | let make = () => {
16 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/Footer.resi:
--------------------------------------------------------------------------------
1 | @react.component
2 | let make: unit => React.element
3 |
--------------------------------------------------------------------------------
/src/Header.res:
--------------------------------------------------------------------------------
1 | module Styles = {
2 | open Emotion
3 | let container = css({
4 | "flexGrow": 0,
5 | "padding": 10,
6 | })
7 | let title = css({
8 | "textAlign": "center",
9 | "margin": 0,
10 | "fontSize": 24,
11 | })
12 | let nav = css({
13 | "display": "flex",
14 | "flexDirection": "row",
15 | "alignItems": "stretch",
16 | "justifyContent": "center",
17 | "padding": 10,
18 | })
19 | let navItem = css({
20 | "padding": 10,
21 | "textDecoration": "none",
22 | "color": "#0091FF",
23 | "borderRadius": 5,
24 | })
25 | let activeNavItem = css({
26 | "backgroundColor": "#0091FF",
27 | "color": "#fff",
28 | })
29 | }
30 |
31 | @react.component
32 | let make = () => {
33 | {"ReScript React Starter Kit"->React.string}
35 |
43 |
18 |
{"Welcome to ReScript React Starter Kit!"->React.string}
20 |21 | > 22 | } 23 | -------------------------------------------------------------------------------- /src/Home.resi: -------------------------------------------------------------------------------- 1 | @react.component 2 | let make: unit => React.element 3 | -------------------------------------------------------------------------------- /src/Robots.res: -------------------------------------------------------------------------------- 1 | module Styles = { 2 | open Emotion 3 | let container = css({ 4 | "fontFamily": "sans-serif", 5 | "flexGrow": 1, 6 | "display": "flex", 7 | "flexDirection": "column", 8 | "alignItems": "stretch", 9 | "justifyContent": "center", 10 | "width": "100%", 11 | "maxWidth": 600, 12 | "margin": "auto", 13 | }) 14 | let actionButton = css({ 15 | "borderStyle": "none", 16 | "background": "hotpink", 17 | "fontFamily": "inherit", 18 | "color": "#fff", 19 | "fontSize": 20, 20 | "padding": 10, 21 | "cursor": "pointer", 22 | "borderRadius": 10, 23 | "alignSelf": "center", 24 | }) 25 | let disabledButton = cx([actionButton, css({"opacity": "0.3"})]) 26 | let results = css({ 27 | "height": 200, 28 | "backgroundColor": "#efefef", 29 | "borderRadius": 10, 30 | "width": "100%", 31 | "padding": 10, 32 | }) 33 | } 34 | 35 | let fetchRobotsTxt = () => 36 | Request.make(~url=`${Router.publicPath}robots.txt`, ~responseType=Text, ()) 37 | // We can make the loading state more obvious by making the request a bit longer 38 | ->Future.flatMap(~propagateCancel=true, value => { 39 | Future.make(resolve => { 40 | let timeoutId = setTimeout(() => { 41 | resolve(value) 42 | }, 1_000) 43 | Some(() => clearTimeout(timeoutId)) 44 | }) 45 | }) 46 | 47 | @react.component 48 | let make = (~fetchRobotsTxt=fetchRobotsTxt) => { 49 | let (robots, setRobots) = React.useState(() => AsyncData.NotAsked) 50 | 51 | React.useEffect1(() => { 52 | setRobots(_ => Loading) 53 | let request = fetchRobotsTxt() 54 | request->Future.get(payload => { 55 | setRobots(_ => Done(payload)) 56 | }) 57 | // Cancellation is built-in, in our case with the artificial slow down 58 | // It'll cancel both the request and the timer, because we've set the 59 | // `propagateCancel` option to true 60 | Some(() => request->Future.cancel) 61 | }, [fetchRobotsTxt]) 62 | 63 | <> 64 |
65 |
{robots->React.string}84 | | Done(_) => "An error occured"->React.string 85 | }} 86 |
88 | >
89 | }
90 |
--------------------------------------------------------------------------------
/src/Robots.resi:
--------------------------------------------------------------------------------
1 | // The fetchRobotsTxt enables us to pass a mock function for testing
2 | @react.component
3 | let make: (
4 | ~fetchRobotsTxt: unit => Future.t