├── .github
└── workflows
│ └── deploy.yml
├── .gitignore
├── .vscode
├── extensions.json
├── resolve_npm_imports.json
└── settings.json
├── README.md
├── eslintrc.js
├── package.json
├── packages
├── remix-app
│ ├── app
│ │ ├── entry.client.tsx
│ │ ├── entry.server.tsx
│ │ ├── root.tsx
│ │ └── routes
│ │ │ ├── index.tsx
│ │ │ └── rust-demo.tsx
│ ├── package.json
│ ├── remix.config.js
│ └── server.ts
└── rust_functions
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── noop.js
│ ├── package.json
│ └── src
│ ├── lib.rs
│ └── utils.rs
├── patches
└── @remix-run+dev+1.5.1.patch
├── public
└── favicon.ico
└── turbo.json
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: 🤸🏽 Integrate & Deploy
2 | on: [push]
3 | permissions:
4 | actions: write
5 | contents: read
6 |
7 | jobs:
8 | lint:
9 | name: 🤸🏽 lint, build & deploy
10 | runs-on: ubuntu-22.04
11 | permissions:
12 | actions: write
13 | id-token: write # Needed for auth with Deno Deploy
14 | contents: read # Needed to clone the repository
15 | steps:
16 | - name: 🛡 Cancel Previous Runs
17 | uses: styfle/cancel-workflow-action@0.9.1
18 |
19 | - name: 🐑 Clone Git Repo
20 | uses: actions/checkout@v3
21 |
22 | - name: 🦀 Setup Node
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version: 16
26 |
27 | - name: ⛓ Install Node deps
28 | uses: bahmutov/npm-install@v1
29 | with:
30 | useLockFile: false
31 |
32 | - name: 🦕 Setup Deno
33 | uses: denoland/setup-deno@v1
34 | with:
35 | deno-version: v1.22
36 |
37 | - name: 🔬 Lint
38 | run: npm run lint
39 |
40 | - name: 🦀 Install wasm-ack
41 | uses: jetli/wasm-pack-action@v0.3.0
42 |
43 | - name: 💿 Remix Build
44 | run: npm run build
45 | # TODO: vendor..?
46 |
47 | # TODO fork action so only upload public & build dirs
48 | # then skip this step
49 | - name: 🗑️ Throw out deps
50 | run: rm -rf ./packages/remix-app/node_modules
51 |
52 | - name: 🗑️ Throw out more deps
53 | run: rm -rf ./node_modules
54 |
55 | - name: 📂 Make deno and remix dir
56 | run: mkdir -p ./deno
57 |
58 | - name: 📝 Copy Remix build files into outut dir
59 | run: cp -R ./packages/remix-app/{build,public} ./deno
60 | - name: 📝 Copy Rust WASM package files into output dir
61 | run: mkdir -p ./deno/rust_functions/build/ && cp -R ./packages/rust_functions/build/browser ./deno/rust_functions/build
62 |
63 | - name: 🔍 List file contents of root
64 | run: tree ./
65 |
66 | - name: 🔍 List file contents of deno dir
67 | run: tree ./deno
68 |
69 | - name: 🗄 Archive built ouput
70 | uses: actions/upload-artifact@v3
71 | with:
72 | name: Deno Build Package
73 | path: ./deno
74 |
75 | - name: 🚛 Ship It
76 | uses: denoland/deployctl@v1
77 | with:
78 | project: "remix-air-metal-stack"
79 | entrypoint: "./build/index.js"
80 | root: "./deno"
81 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # We don't want lockfiles in stacks, as people could use a different package manager
2 | # This part will be removed by `remix.init`
3 | package-lock.json
4 | yarn.lock
5 | pnpm-lock.yaml
6 | pnpm-lock.yml
7 |
8 | node_modules
9 |
10 | /.cache
11 | /build
12 | /public/build
13 | .env
14 |
15 | /cypress/screenshots
16 | /cypress/videos
17 | /postgres-data
18 |
19 | /app/styles/tailwind.css
20 |
21 | .DS_Store
22 |
23 | // Don't include Rust compiled files
24 | */target/
25 | */__test__/*
26 | */npm/*
27 | */.cache/*
28 | packages/remix-app/public/build/
29 | packages/remix-app/build/
30 | packages/remix-app/.cache
31 | */.turbo/*
32 | turbo-build.log
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "denoland.vscode-deno"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.vscode/resolve_npm_imports.json:
--------------------------------------------------------------------------------
1 | {
2 | "// This import map is used solely for the denoland.vscode-deno extension.": "",
3 | "// Remix does not support import maps.": "",
4 | "// Dependency management is done through `npm` and `node_modules/` instead.": "",
5 | "// Deno-only dependencies may be imported via URL imports (without using import maps).": "",
6 |
7 | "imports": {
8 | "// `@remix-run/deno` code is already a Deno module, so just get types for it directly from `node_modules/`": "",
9 | "@remix-run/deno": "../node_modules/@remix-run/deno/index.ts",
10 | "@remix-run/dev/server-build": "https://esm.sh/@remix-run/dev@1.5.0/server-build",
11 | "@remix-run/react": "https://esm.sh/@remix-run/react@1.5.0",
12 | "react": "https://esm.sh/react@17.0.2",
13 | "react-dom": "https://esm.sh/react-dom@17.0.2",
14 | "react-dom/server": "https://esm.sh/react-dom@17.0.2/server"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "deno.enable": true,
3 | "deno.importMap": "./.vscode/resolve_npm_imports.json",
4 | "deno.lint": true
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Remix + Deno + Rust -> Webassembly - The Air Metal Stack
2 |
3 | Welcome to the Air Metal Stack for Remix! 🦕 + 🦀
4 | This stack is a good choice if you want to run on Deno, deploy to Deno Deploy, and use Rust compiled to WASM for certain functions.
5 |
6 | This is a monorepo, with a package for Rust compiled to WASM called `rust_functions`, and a package for your Remix app called `remix-app`. Both of these get built using Turborepo.
7 |
8 | There is a [demo](https://remix-air-metal-stack.deno.dev/) where you can see WASM running both on the worker via an action and on the client with an alert popup.
9 |
10 | For more, check out the [Remix docs](https://remix.run/docs), the [wasm-pack docs](https://rustwasm.github.io/wasm-pack/), and the [Rust page](https://www.rust-lang.org/).
11 |
12 | ## Install
13 |
14 | ```sh
15 | npx create-remix@latest --template benwis/air-metal-stack
16 | ```
17 |
18 | ## Managing dependencies
19 |
20 | Read about [how we recommend to manage dependencies for Remix projects using Deno](https://github.com/remix-run/remix/blob/main/decisions/0001-use-npm-to-manage-npm-dependencies-for-deno-projects.md).
21 |
22 | - ✅ You should use `npm` to install NPM packages
23 | ```sh
24 | npm install react
25 | ```
26 | ```ts
27 | import { useState } from "react";
28 | ```
29 | - ✅ You may use inlined URL imports or [deps.ts](https://deno.land/manual/examples/manage_dependencies#managing-dependencies) for Deno modules.
30 | ```ts
31 | import { copy } from "https://deno.land/std@0.138.0/streams/conversion.ts";
32 | ```
33 | - ❌ Do not use [import maps](https://deno.land/manual/linking_to_external_code/import_maps).
34 |
35 | ## Setting Up Rust
36 |
37 | 1. Install the Rust language and it's associated tools. You only need to run this once, as it installs globally. If you already have Rust installed, you can skip this step.
38 | ```sh
39 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
40 | ```
41 |
42 | 2. Install wasm-pack to wrap your compiled WASM code in a TS wrapper. The command for Mac and Linux is below. If you're on Windows, visit [this link](https://rustwasm.github.io/wasm-pack/installer/#) for an exe. You only need to run this once, as it installs globally. If you already have wasm-pack installed, you can skip this step.
43 | ```sh
44 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
45 | ```
46 | If you have issues with a lack of precompiled binaries for your platform(on an M1 Mac for example), you can just have cargo compile and install it with the below command.
47 | ```sh
48 | cargo install wasm-pack
49 | ```
50 |
51 | 3. Install cargo-watch to allow the Rust code to compile on changes in dev mode
52 | ```sh
53 | cargo install cargo-watch
54 | ```
55 | ## Development
56 |
57 | From your terminal in the project root:
58 |
59 | ```sh
60 | npm run build
61 | npm run dev
62 | ```
63 |
64 | This starts your app in development mode, rebuilding TS and Rust assets on file changes.
65 |
66 | ### Type hints
67 |
68 | This template provides type hinting to VS Code via a [dedicated import map](./.vscode/resolve_npm_imports.json).
69 |
70 | To get types in another editor, use an extension for Deno that supports import maps and point your editor to `./.vscode/resolve_npm_imports.json`.
71 |
72 | For more, see [our decision doc for interop between Deno and NPM](https://github.com/remix-run/remix/blob/main/decisions/0001-use-npm-to-manage-npm-dependencies-for-deno-projects.md#vs-code-type-hints).
73 |
74 | ## Production
75 |
76 | First, build your app for production:
77 |
78 | ```sh
79 | npm run build
80 | ```
81 |
82 | Then run the app in production mode:
83 |
84 | ```sh
85 | npm start
86 | ```
87 |
88 | ## Deployment
89 |
90 | Building the Deno app (`npm run build`) results in two outputs:
91 |
92 | - `packages/remix-app/build/` (server bundle)
93 | - `packages/remix-app/public/build/` (browser bundle)
94 | - `packages/rust_functions/build/browser` (WASM browser bundle)
95 |
96 | You can deploy these bundles to any host that runs Deno, but here we'll focus on deploying to [Deno Deploy](https://deno.com/deploy).
97 |
98 | ## Setting up Deno Deploy
99 |
100 | 1. [Sign up](https://dash.deno.com/signin) for Deno Deploy.
101 |
102 | 2. [Create a new Deno Deploy project](https://dash.deno.com/new) for this app.
103 |
104 | 3. We use a Github Action to deploy our project's build artifacts to Deno Deploy. To enable this functionality, you must go to your project's settings in Deno and link your Github repo in manual mode.
105 |
106 | 4. Add a DENO_ENV environment variable to your Deno Deploy project with a value of `production`. This allows us to know when we're running in production and correctly resolve the path to the WASM files.
107 |
108 |
109 | ### Deploying to Deno Deploy
110 |
111 | After you've set up Deno Deploy, simply push to your Github repo. It should push your changes over to Deno Deploy. Check the Action in your Github Account, or the Deno Deploy project page for confirmation
112 |
113 | ### Changing Things
114 | - If you'd like to change the name of the Rust crate, be careful to change it in the following places
115 | - `packages/remix-app/server.ts`
116 | - `packages/remix-app/routes/rust-demo.tsx`
117 | - `packages/remix-app/entry.client.tsx`
118 | - `packages/remix-app/entry.server.tsx`
119 | - `packages/remix-app/package.json`
120 |
121 | ### Notes
122 |
123 | - Remix assumes that the public, build, and rust_functions folders will be in the root of the project on Deno Deploy. Changing that structure may lead to errors in production. Caution is advised.
--------------------------------------------------------------------------------
/eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | settings: {
6 | next: {
7 | rootDir: ["apps/*/"],
8 | },
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "air-metal-stacl",
3 | "version": "0.0.1",
4 | "private": true,
5 | "sideEffects": false,
6 | "workspaces": [
7 | "config/*",
8 | "packages/*"
9 | ],
10 | "scripts": {
11 | "build": "npx --yes turbo run build",
12 | "dev": "npx --yes turbo run dev --parallel",
13 | "lint": "npx --yes turbo run lint",
14 | "typecheck": "npx --yes turbo run typecheck",
15 | "format": "prettier --write \"**/*.{ts,tsx,js,jsx,md}\"",
16 | "postinstall": "patch-package",
17 | "deploy": "cd packages && deployctl deploy --prod --include=remix-app,rust_functions --exclude=node_modules --project=remix-air-metal-stack ./remix-app/build/index.js"
18 | },
19 | "devDependencies": {
20 | "patch-package": "^6.4.7",
21 | "prettier": "^2.6.2",
22 | "prisma": "^3.14.0",
23 | "typescript": "^4.7.2"
24 | },
25 | "engines": {
26 | "npm": ">=7.0.0",
27 | "node": ">=14.0.0"
28 | },
29 | "packageManager": "npm@8.5.5"
30 | }
31 |
--------------------------------------------------------------------------------
/packages/remix-app/app/entry.client.tsx:
--------------------------------------------------------------------------------
1 | import { RemixBrowser } from "@remix-run/react";
2 | import * as React from "react";
3 | import { hydrateRoot } from "react-dom/client";
4 | import init from "../../rust_functions/build/browser/rust_functions"
5 | import wasm from "../../rust_functions/build/browser/rust_functions_bg.wasm"
6 |
7 | init(wasm).then(() => {
8 | hydrateRoot(document,