├── .gitignore
├── README.md
├── app
├── entry.client.res
├── entry.server.res
├── res-routes
│ ├── gists.res
│ └── index.res
└── root.res
├── bsconfig.json
├── jsconfig.json
├── package-lock.json
├── package.json
├── patches
└── @remix-run+dev+1.1.1.patch
├── public
└── favicon.ico
└── remix.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /.cache
4 | /build
5 | /public/build
6 | .env
7 |
8 | /app/**/*.jsx
9 | /app/**/*.js
10 | /lib/
11 | .bsb.lock
12 | .merlin
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ReScript Remix Template
2 |
3 | A starter template for writing Remix apps in [ReScript](https://rescript-lang.org/).
4 |
5 | ## Development
6 |
7 | ```
8 | npm i
9 | npm run dev:rescript
10 | ```
11 |
12 | And then in another terminal:
13 |
14 | ```
15 | npm run dev:remix
16 | ```
17 |
18 | ## Building
19 |
20 | ```
21 | npm run build
22 | ```
23 |
--------------------------------------------------------------------------------
/app/entry.client.res:
--------------------------------------------------------------------------------
1 | @val external document: Dom.element = "document"
2 |
3 | ReactDOM.hydrate( , document)
4 |
--------------------------------------------------------------------------------
/app/entry.server.res:
--------------------------------------------------------------------------------
1 | module ResponseInit = {
2 | type t
3 |
4 | external make: {..} => t = "%identity"
5 | }
6 |
7 | // TODO: Swap out for Webapi.Fetch.Response when it supports construction
8 | // See https://github.com/tinymce/rescript-webapi/issues/63
9 | @new
10 | external makeResponse: (Webapi.Fetch.BodyInit.t, ResponseInit.t) => Webapi.Fetch.Response.t =
11 | "Response"
12 |
13 | let default = (request, responseStatusCode, responseHeaders, remixContext) => {
14 | open Webapi
15 |
16 | let markup = ReactDOMServer.renderToString(
17 | Fetch.Request.url} />,
18 | )
19 |
20 | responseHeaders->Fetch.Headers.set("Content-Type", "text/html")
21 |
22 | makeResponse(
23 | Fetch.BodyInit.make("" ++ markup),
24 | ResponseInit.make({
25 | "status": responseStatusCode,
26 | "headers": responseHeaders,
27 | }),
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/app/res-routes/gists.res:
--------------------------------------------------------------------------------
1 | type gist = {
2 | html_url: string,
3 | id: string,
4 | }
5 |
6 | type loaderData = array
7 |
8 | // Explicit return type for type safety
9 | let loader = (): Promise.t =>
10 | Webapi.Fetch.fetch("https://api.github.com/gists")
11 | ->Promise.then(res => res->Webapi.Fetch.Response.json)
12 | ->Promise.thenResolve(json =>
13 | json
14 | ->Js.Json.decodeArray
15 | ->Belt.Option.map(gists =>
16 | gists->Js.Array2.map(gist => {
17 | let gist = gist->Js.Json.decodeObject->Belt.Option.getUnsafe
18 |
19 | {
20 | html_url: gist
21 | ->Js.Dict.get("html_url")
22 | ->Belt.Option.flatMap(Js.Json.decodeString)
23 | ->Belt.Option.getExn,
24 | id: gist
25 | ->Js.Dict.get("id")
26 | ->Belt.Option.flatMap(Js.Json.decodeString)
27 | ->Belt.Option.getExn,
28 | }
29 | })
30 | )
31 | ->Belt.Option.getUnsafe
32 | )
33 |
34 | let default = () => {
35 | let gists: loaderData = Remix.useLoaderData()
36 |
37 |
44 | }
45 |
--------------------------------------------------------------------------------
/app/res-routes/index.res:
--------------------------------------------------------------------------------
1 | @react.component
2 | let default = () =>
3 |
4 |
{"Welcome to Remix"->React.string}
5 |
22 |
23 |
--------------------------------------------------------------------------------
/app/root.res:
--------------------------------------------------------------------------------
1 | let meta = () =>
2 | {
3 | "title": "New Remix App",
4 | }
5 |
6 | @react.component
7 | let default = () =>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {if Remix.process["env"]["NODE_ENV"] === "development" {
20 |
21 | } else {
22 | React.null
23 | }}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/bsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rescript-project-template",
3 | "version": "0.0.1",
4 | "sources": {
5 | "dir": "app",
6 | "subdirs": true
7 | },
8 | "package-specs": {
9 | "module": "es6",
10 | "in-source": true
11 | },
12 | "suffix": ".js",
13 | "reason": {
14 | "react-jsx": 3
15 | },
16 | "bs-dependencies": [
17 | "@rescript/react",
18 | "rescript-remix",
19 | "rescript-webapi",
20 | "@ryyppy/rescript-promise"
21 | ],
22 | "warnings": {
23 | "error": "+101"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "baseUrl": ".",
5 | "paths": {
6 | "~/*": ["./app/*"]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "remix-app-template-js",
4 | "description": "",
5 | "license": "",
6 | "scripts": {
7 | "build": "rescript build && remix build",
8 | "dev:remix": "remix dev",
9 | "dev:rescript": "rescript build -w",
10 | "postinstall": "patch-package && remix setup node",
11 | "start": "remix-serve build"
12 | },
13 | "dependencies": {
14 | "@remix-run/react": "^1.1.1",
15 | "@remix-run/serve": "^1.1.1",
16 | "@rescript/react": "^0.10.3",
17 | "@ryyppy/rescript-promise": "^2.1.0",
18 | "react": "^17.0.2",
19 | "react-dom": "^17.0.2",
20 | "remix": "^1.1.1",
21 | "rescript-remix": "^0.2.1",
22 | "rescript-webapi": "^0.2.0"
23 | },
24 | "devDependencies": {
25 | "@remix-run/dev": "^1.1.1",
26 | "patch-package": "^6.4.7",
27 | "rescript": "^9.1.4"
28 | },
29 | "engines": {
30 | "node": ">=14"
31 | },
32 | "sideEffects": false
33 | }
34 |
--------------------------------------------------------------------------------
/patches/@remix-run+dev+1.1.1.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@remix-run/dev/compiler.js b/node_modules/@remix-run/dev/compiler.js
2 | index 5dc3ee5..d6daef3 100644
3 | --- a/node_modules/@remix-run/dev/compiler.js
4 | +++ b/node_modules/@remix-run/dev/compiler.js
5 | @@ -348,8 +348,10 @@ async function createServerBuild(config, options) {
6 | if (id === "./assets.json" && importer === "") return true; // Mark all bare imports as external. They will be require()'d (or
7 | // imported if ESM) at runtime from node_modules.
8 |
9 | +
10 | if (isBareModuleId(id)) {
11 | let packageName = getNpmPackageName(id);
12 | + if (config.transpileModules.includes(packageName)) return false;
13 |
14 | if (!/\bnode_modules\b/.test(importer) && !module$1.builtinModules.includes(packageName) && !dependencies$1.includes(packageName)) {
15 | options.onWarning(`The path "${id}" is imported in ` + `${path__namespace.relative(process.cwd(), importer)} but ` + `${packageName} is not listed in your package.json dependencies. ` + `Did you forget to install it?`, packageName);
16 | diff --git a/node_modules/@remix-run/dev/config.js b/node_modules/@remix-run/dev/config.js
17 | index 9705888..398f3e6 100644
18 | --- a/node_modules/@remix-run/dev/config.js
19 | +++ b/node_modules/@remix-run/dev/config.js
20 | @@ -135,7 +135,8 @@ async function readConfig(remixRoot, serverMode = serverModes.ServerMode.Product
21 | serverMode,
22 | serverModuleFormat,
23 | serverPlatform,
24 | - mdx
25 | + mdx,
26 | + transpileModules: appConfig.transpileModules || [],
27 | };
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tom-sherman/rescript-remix-template/45c1a244413064f126d4f9f59ed12cd55cdbf06f/public/favicon.ico
--------------------------------------------------------------------------------
/remix.config.js:
--------------------------------------------------------------------------------
1 | const { registerRoutes } = require('rescript-remix/registerRoutes');
2 |
3 | /**
4 | * @type {import('@remix-run/dev/config').AppConfig}
5 | */
6 | module.exports = {
7 | appDirectory: "app",
8 | assetsBuildDirectory: "public/build",
9 | publicPath: "/build/",
10 | serverBuildDirectory: "build",
11 | devServerPort: 8002,
12 | ignoredRouteFiles: [".*", "*.res"],
13 | transpileModules: ["rescript"],
14 | routes(defineRoutes) {
15 | return defineRoutes(route => {
16 | registerRoutes(route);
17 | });
18 | }
19 | };
20 |
--------------------------------------------------------------------------------