├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── .editorconfig
├── .eslintrc.json
├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitpod.yml
├── LICENSE
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public
└── favicon.ico
├── src
├── components
│ └── WASMExample.tsx
├── context
│ └── WASM.tsx
├── hooks
│ └── useMountEffectOnce.ts
├── pages
│ ├── _app.tsx
│ └── index.tsx
└── styles
│ ├── Home.module.css
│ └── globals.css
├── tsconfig.json
└── wasm
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── rs-test.sh
└── src
└── lib.rs
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile
2 |
3 | # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
4 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-16-bullseye
5 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node
3 | {
4 | "name": "nextjs-wasm",
5 | "build": {
6 | "dockerfile": "Dockerfile"
7 | },
8 |
9 | // Add the IDs of extensions you want installed when the container is created.
10 | "extensions": [
11 | "rust-lang.rust-analyzer",
12 | "bungcip.better-toml",
13 | "serayuzgur.crates",
14 | "EditorConfig.EditorConfig"
15 | ],
16 |
17 | // Use 'postCreateCommand' to run commands after the container is created.
18 | "postCreateCommand": "cargo install wasm-pack",
19 |
20 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
21 | "remoteUser": "node",
22 | "features": {
23 | "rust": "latest"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | insert_final_newline = true
8 |
9 | [*.{js,jsx,ts,tsx,json}]
10 | indent_style = space
11 | indent_size = 2
12 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 | version: 2
6 | updates:
7 | - package-ecosystem: "npm"
8 | directory: "/"
9 | schedule:
10 | interval: "monthly"
11 |
12 | - package-ecosystem: "cargo"
13 | directory: "/wasm/"
14 | schedule:
15 | interval: "monthly"
16 |
17 | - package-ecosystem: "github-actions"
18 | directory: "/" # default location of `.github/workflows`
19 | schedule:
20 | interval: "monthly"
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | webassembly:
11 | name: WebAssembly
12 | runs-on: ubuntu-latest
13 | env:
14 | CARGO_TERM_COLOR: always
15 | RUST_TEST_THREADS: 32
16 | defaults:
17 | run:
18 | working-directory: wasm
19 | steps:
20 | - uses: actions/checkout@v3
21 | - uses: actions/cache@v3
22 | with:
23 | path: |
24 | ~/.cargo/bin/
25 | ~/.cargo/registry/index/
26 | ~/.cargo/registry/cache/
27 | ~/.cargo/git/db/
28 | target/
29 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
30 | - name: Tests
31 | run: ./rs-test.sh
32 |
33 | nextjs:
34 | name: NextJS
35 | runs-on: ubuntu-latest
36 | steps:
37 | - uses: actions/checkout@v3
38 | - uses: actions/cache@v3
39 | with:
40 | path: |
41 | ~/.cargo/bin/
42 | ~/.cargo/registry/index/
43 | ~/.cargo/registry/cache/
44 | ~/.cargo/git/db/
45 | ./wasm/target/
46 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
47 | - uses: actions/setup-node@v3
48 | with:
49 | node-version: 16.x
50 | cache: 'npm'
51 | cache-dependency-path: ./package-lock.json
52 | - name: Install dependencies
53 | run: npm ci
54 | - name: Build WebAssembly
55 | run: npm run build:wasm
56 | - name: Build NextJS application
57 | run: npm run build
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | image: gitpod/workspace-full
2 |
3 | tasks:
4 | - init: |
5 | cargo install wasm-pack
6 | npm run build:wasm
7 | npm install
8 | npm run dev
9 | vscode:
10 | extensions:
11 | - matklad.rust-analyzer
12 | - bungcip.better-toml
13 | - serayuzgur.crates
14 | - EditorConfig.EditorConfig
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 George Satellite
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next.js with WebAssembly
2 |
3 | [](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
4 |
5 | [Next.js](https://nextjs.org/)-based web application template with [WebAssembly](https://webassembly.org/) module written in [Rust](https://www.rust-lang.org/) programming language.
6 |
7 | ## Try in [Gitpod](https://www.gitpod.io/)
8 |
9 | [](https://gitpod.io/#https://github.com/satelllte/nextjs-wasm)
10 |
11 | ## Try in [GitHub Codespaces](https://github.com/features/codespaces)
12 |
13 | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/satelllte/nextjs-wasm)
14 |
15 | ## Prerequisites
16 |
17 | - [NodeJS](https://nodejs.org/) | recommended version: `>= 16`
18 | - [Rust & Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) | recommended version: `>= 1.61`
19 |
20 | ## Getting Started
21 |
22 | Install dependencies:
23 |
24 | ```bash
25 | npm install
26 | ```
27 |
28 | Compile WebAssembly:
29 |
30 | ```bash
31 | npm run build:wasm
32 | ```
33 |
34 | Run the development server:
35 |
36 | ```bash
37 | npm run dev
38 | ```
39 |
40 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
41 |
42 | ## Build for Production
43 |
44 | ```bash
45 | npm run build:wasm # if wasn't built yet
46 | npm run build
47 | ```
48 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-wasm",
3 | "version": "1.4.0",
4 | "scripts": {
5 | "dev": "next dev",
6 | "build:wasm": "wasm-pack build wasm --release --target web",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "next": "13.0.5",
13 | "react": "18.2.0",
14 | "react-dom": "18.2.0",
15 | "wasm": "file:wasm/pkg"
16 | },
17 | "devDependencies": {
18 | "@types/node": "18.11.9",
19 | "@types/react": "18.0.25",
20 | "@types/react-dom": "18.0.9",
21 | "eslint": "8.28.0",
22 | "eslint-config-next": "13.0.5",
23 | "typescript": "4.9.3",
24 | "wasm-pack": "0.10.3"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/satelllte/nextjs-wasm/2a62968bb10a7299f59d35499e5b61048bc00548/public/favicon.ico
--------------------------------------------------------------------------------
/src/components/WASMExample.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from "react"
2 | import { WASMContext } from "../context/WASM"
3 |
4 | export const WASMExample = () => {
5 | const ctx = useContext(WASMContext)
6 |
7 | if (!ctx.wasm) {
8 | return <>...>
9 | }
10 |
11 | return <>Computed from WASM: 4+3={ctx.wasm.add(4,3)}>
12 | }
13 |
--------------------------------------------------------------------------------
/src/context/WASM.tsx:
--------------------------------------------------------------------------------
1 | import { useState, createContext } from 'react'
2 | import type { ReactNode } from 'react'
3 | import { useMountEffectOnce } from '../hooks/useMountEffectOnce'
4 |
5 | const initial: IWASMContext = {}
6 |
7 | export const WASMContext = createContext(initial)
8 |
9 | export const WASMContextProvider: React.FC = ({
10 | children
11 | }) => {
12 | const [state, setState] = useState(initial)
13 |
14 | // This has to run only once: https://github.com/rustwasm/wasm-bindgen/issues/3153
15 | // Though, in development React renders twice when Strict Mode is enabled: https://reactjs.org/docs/strict-mode.html
16 | // That's why it must be limited to a single mount run
17 | useMountEffectOnce(() => {
18 | (async() => {
19 | const wasm = await import("wasm");
20 | await wasm.default();
21 | setState({ wasm });
22 | })()
23 | })
24 |
25 | return (
26 |
27 | {children}
28 |
29 | )
30 | }
31 |
32 | interface IWASMContext {
33 | wasm?: typeof import('wasm')
34 | }
35 |
36 | interface WASMContextProviderProps {
37 | children: ReactNode
38 | }
39 |
--------------------------------------------------------------------------------
/src/hooks/useMountEffectOnce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react'
2 |
3 | export const useMountEffectOnce = (fn: () => void) => {
4 | const wasExecutedRef = useRef(false)
5 | useEffect(() => {
6 | if (!wasExecutedRef.current) {
7 | fn()
8 | }
9 | wasExecutedRef.current = true
10 | }, [fn])
11 | }
12 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 | import type { AppProps } from 'next/app'
3 | import { WASMContextProvider } from '../context/WASM'
4 |
5 | const App = ({ Component, pageProps }: AppProps) => {
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 |
13 | export default App
14 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import styles from '../styles/Home.module.css'
4 | import { WASMExample } from '../components/WASMExample'
5 |
6 | const Home: NextPage = () => {
7 | return (
8 |
9 |
10 |
Next.JS with WebAssembly
11 |
12 |
13 |
14 |
15 |
16 |
17 | Welcome to Next.js with WebAssembly!
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | export default Home
29 |
--------------------------------------------------------------------------------
/src/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 0 2rem;
3 | }
4 |
5 | .main {
6 | min-height: 100vh;
7 | padding: 4rem 0;
8 | flex: 1;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .title a {
16 | color: #0070f3;
17 | text-decoration: none;
18 | }
19 |
20 | .title a:hover,
21 | .title a:focus,
22 | .title a:active {
23 | text-decoration: underline;
24 | }
25 |
26 | .title {
27 | margin: 0;
28 | line-height: 1.15;
29 | font-size: 4rem;
30 | text-align: center;
31 | }
32 |
33 | .wasm {
34 | margin: 4rem 0;
35 | line-height: 1.5;
36 | font-size: 1.5rem;
37 | text-align: center;
38 | }
39 |
40 | @media (max-width: 600px) {
41 | .title {
42 | font-size: 3rem;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/wasm/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /pkg
--------------------------------------------------------------------------------
/wasm/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "bumpalo"
7 | version = "3.9.1"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
10 |
11 | [[package]]
12 | name = "cfg-if"
13 | version = "1.0.0"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
16 |
17 | [[package]]
18 | name = "log"
19 | version = "0.4.17"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
22 | dependencies = [
23 | "cfg-if",
24 | ]
25 |
26 | [[package]]
27 | name = "once_cell"
28 | version = "1.13.0"
29 | source = "registry+https://github.com/rust-lang/crates.io-index"
30 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
31 |
32 | [[package]]
33 | name = "proc-macro2"
34 | version = "1.0.39"
35 | source = "registry+https://github.com/rust-lang/crates.io-index"
36 | checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
37 | dependencies = [
38 | "unicode-ident",
39 | ]
40 |
41 | [[package]]
42 | name = "quote"
43 | version = "1.0.18"
44 | source = "registry+https://github.com/rust-lang/crates.io-index"
45 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
46 | dependencies = [
47 | "proc-macro2",
48 | ]
49 |
50 | [[package]]
51 | name = "syn"
52 | version = "1.0.95"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
55 | dependencies = [
56 | "proc-macro2",
57 | "quote",
58 | "unicode-ident",
59 | ]
60 |
61 | [[package]]
62 | name = "unicode-ident"
63 | version = "1.0.0"
64 | source = "registry+https://github.com/rust-lang/crates.io-index"
65 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
66 |
67 | [[package]]
68 | name = "wasm"
69 | version = "1.4.0"
70 | dependencies = [
71 | "wasm-bindgen",
72 | ]
73 |
74 | [[package]]
75 | name = "wasm-bindgen"
76 | version = "0.2.83"
77 | source = "registry+https://github.com/rust-lang/crates.io-index"
78 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
79 | dependencies = [
80 | "cfg-if",
81 | "wasm-bindgen-macro",
82 | ]
83 |
84 | [[package]]
85 | name = "wasm-bindgen-backend"
86 | version = "0.2.83"
87 | source = "registry+https://github.com/rust-lang/crates.io-index"
88 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
89 | dependencies = [
90 | "bumpalo",
91 | "log",
92 | "once_cell",
93 | "proc-macro2",
94 | "quote",
95 | "syn",
96 | "wasm-bindgen-shared",
97 | ]
98 |
99 | [[package]]
100 | name = "wasm-bindgen-macro"
101 | version = "0.2.83"
102 | source = "registry+https://github.com/rust-lang/crates.io-index"
103 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
104 | dependencies = [
105 | "quote",
106 | "wasm-bindgen-macro-support",
107 | ]
108 |
109 | [[package]]
110 | name = "wasm-bindgen-macro-support"
111 | version = "0.2.83"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
114 | dependencies = [
115 | "proc-macro2",
116 | "quote",
117 | "syn",
118 | "wasm-bindgen-backend",
119 | "wasm-bindgen-shared",
120 | ]
121 |
122 | [[package]]
123 | name = "wasm-bindgen-shared"
124 | version = "0.2.83"
125 | source = "registry+https://github.com/rust-lang/crates.io-index"
126 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
127 |
--------------------------------------------------------------------------------
/wasm/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wasm"
3 | version = "1.4.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [lib]
9 | crate-type = ["cdylib"]
10 |
11 | [dependencies]
12 | "wasm-bindgen" = "0.2.83"
13 |
--------------------------------------------------------------------------------
/wasm/rs-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cargo test
--------------------------------------------------------------------------------
/wasm/src/lib.rs:
--------------------------------------------------------------------------------
1 | use wasm_bindgen::prelude::*;
2 |
3 | #[wasm_bindgen]
4 | pub fn add(x: i32, y: i32) -> i32 {
5 | x + y
6 | }
7 |
8 | #[cfg(test)]
9 | mod tests {
10 | use super::add;
11 |
12 | #[test]
13 | fn _add() {
14 | assert_eq!(add(2, 2), 4);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------