├── .cargo
└── config.toml
├── .editorconfig
├── .github
└── workflows
│ ├── build-and-test.yml
│ └── release.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── Cross.toml
├── LICENSE
├── README.md
├── assets
├── winterjs-logo-black.png
└── winterjs-logo-white.png
├── benchmark
├── .gitignore
├── README.md
├── bun-simple.js
├── complex.js
├── node-simple.js
├── simple.js
└── worker.capnp
├── build-debug.sh
├── build-single-threaded.sh
├── build.rs
├── build.sh
├── docs
├── References.md
└── spidermonkey_cookbook.cpp
├── examples
├── echo
│ ├── app.yaml
│ ├── app
│ │ └── app.js
│ └── wasmer.toml
├── hello-world.js
└── http-echo
│ ├── README.md
│ ├── app.yaml
│ ├── src
│ └── index.js
│ └── wasmer.toml
├── flake.lock
├── flake.nix
├── package-lock.json
├── package.json
├── rust-toolchain
├── shell.nix
├── src
├── builtins
│ ├── cache
│ │ ├── cache_storage.rs
│ │ └── mod.rs
│ ├── core
│ │ ├── core.js
│ │ └── mod.rs
│ ├── crypto
│ │ ├── mod.rs
│ │ └── subtle
│ │ │ ├── algorithm
│ │ │ ├── hmac.rs
│ │ │ ├── md5.rs
│ │ │ ├── mod.rs
│ │ │ └── sha.rs
│ │ │ ├── crypto_key.rs
│ │ │ ├── jwk.rs
│ │ │ └── mod.rs
│ ├── internal_js_modules.rs
│ ├── internal_js_modules
│ │ ├── __types
│ │ │ └── __winterjs_core_.d.ts
│ │ ├── __winterjs_internal
│ │ │ ├── base64-js.js
│ │ │ └── ieee754.js
│ │ ├── node
│ │ │ ├── async_hooks.js
│ │ │ ├── async_hooks.ts
│ │ │ └── buffer.js
│ │ └── tsconfig.json
│ ├── js_globals.rs
│ ├── js_globals
│ │ ├── event.js
│ │ ├── event.ts
│ │ ├── readable-stream.js
│ │ └── tsconfig.json
│ ├── mod.rs
│ ├── navigator.rs
│ ├── performance.rs
│ └── process.rs
├── main.rs
├── request_handlers
│ ├── cloudflare
│ │ ├── context.rs
│ │ ├── env.rs
│ │ ├── mod.rs
│ │ └── routes.rs
│ ├── mod.rs
│ ├── service_workers
│ │ ├── event_listener.rs
│ │ ├── fetch_event.rs
│ │ └── mod.rs
│ └── wintercg.rs
├── runners
│ ├── event_loop_stream.rs
│ ├── exec.rs
│ ├── inline.rs
│ ├── mod.rs
│ ├── request_loop.rs
│ ├── request_queue.rs
│ ├── single.rs
│ └── watch.rs
├── server.rs
└── sm_utils.rs
├── test-suite
├── Cargo.lock
├── Cargo.toml
├── js-test-app
│ ├── .gitignore
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── rollup.config.js
│ └── src
│ │ ├── main.js
│ │ ├── test-files
│ │ ├── 1-hello.js
│ │ ├── 10-atob-btoa.js
│ │ ├── 11-fetch.js
│ │ ├── 11.1-fetch-body.js
│ │ ├── 12-streams.js
│ │ ├── 12.1-transform-stream.js
│ │ ├── 12.2-text-encoder-stream.js
│ │ ├── 12.3-text-decoder-stream.js
│ │ ├── 13-performance.js
│ │ ├── 14-form-data.js
│ │ ├── 15-timers.js
│ │ ├── 16-crypto.js
│ │ ├── 16.1-crypto-hmac.js
│ │ ├── 16.2-crypto-sha.js
│ │ ├── 17-cache.js
│ │ ├── 18-event.js
│ │ ├── 19-abort.js
│ │ ├── 2-blob.js
│ │ ├── 2.1-file.js
│ │ ├── 3-headers.js
│ │ ├── 4-request.js
│ │ ├── 5-response.js
│ │ ├── 6-text-encoder.js
│ │ ├── 7-text-decoder.js
│ │ ├── 8-url.js
│ │ ├── 8.1-search-params.js
│ │ └── 9-wait-until.js
│ │ └── test-utils.js
├── src
│ ├── lib.rs
│ └── main.rs
└── winterjs-tests.toml
├── tests
├── complex.js
├── edge-template.js
├── fetch.js
├── promise.js
├── simple.js
└── wrangler.js
└── wasmer.toml
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.x86_64-pc-windows-msvc]
2 | linker = "lld-link.exe"
3 |
4 | [target.i686-pc-windows-msvc]
5 | linker = "lld-link.exe"
6 |
7 | [target.aarch64-pc-windows-msvc]
8 | linker = "lld-link.exe"
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.js]
2 | indent_style = space
3 | indent_size = 2
--------------------------------------------------------------------------------
/.github/workflows/build-and-test.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | workflow_call:
5 | pull_request:
6 | push:
7 | branches:
8 | - main
9 |
10 | jobs:
11 | build-and-test:
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | metadata: [
16 | {
17 | name: "Ubuntu (x86 native)",
18 | os: ubuntu-latest,
19 | bin-path: target/release-compact/winterjs,
20 | artifact-name: winterjs-linux-x86,
21 | target: native,
22 | },
23 | {
24 | name: "macOS (arm native)",
25 | os: macos-latest,
26 | bin-path: target/release-compact/winterjs,
27 | artifact-name: winterjs-macos-arm,
28 | target: native,
29 | },
30 | {
31 | name: "macOS (x86 native)",
32 | os: macos-13,
33 | bin-path: target/release-compact/winterjs,
34 | artifact-name: winterjs-macos-x86,
35 | target: native,
36 | },
37 | {
38 | name: "Ubuntu (wasix)",
39 | os: ubuntu-latest,
40 | bin-path: target/wasm32-wasmer-wasi/release/winterjs.wasm,
41 | artifact-name: winterjs-wasix,
42 | target: wasix,
43 | wasix-toolchain-release-asset: rust-toolchain-x86_64-unknown-linux-gnu.tar.gz
44 | },
45 | # {
46 | # name: "macOS (wasix)",
47 | # os: macos-latest,
48 | # bin-path: target/release-compact/winterjs,
49 | # artifact-name: winterjs-macos-wasix,
50 | # target: wasix,
51 | # wasix-toolchain-release-asset: rust-toolchain-aarch64-apple-darwin.tar.gz
52 | # }
53 | ]
54 | name: Build and Test - ${{ matrix.metadata.name }}
55 | runs-on: ${{ matrix.metadata.os }}
56 | steps:
57 | - name: Check out
58 | uses: actions/checkout@v3
59 | with:
60 | submodules: "recursive"
61 |
62 | - name: OS Setup (Ubuntu)
63 | if: ${{ matrix.metadata.os == 'ubuntu-latest' }}
64 | run: |
65 | sudo apt-get update
66 | sudo apt-get install -y build-essential python3.12 python3-distutils-extra llvm-15 libclang-dev clang-15 wabt
67 | npm i -g wasm-opt pnpm concurrently
68 | sudo rm /usr/bin/clang
69 | sudo rm /usr/bin/clang++
70 | sudo ln -s /usr/bin/clang-15 /usr/bin/clang
71 | sudo ln -s /usr/bin/clang++-15 /usr/bin/clang++
72 | sudo ln -s /usr/bin/llvm-ar-15 /usr/bin/llvm-ar
73 | sudo ln -s /usr/bin/llvm-nm-15 /usr/bin/llvm-nm
74 | sudo ln -s /usr/bin/llvm-ranlib-15 /usr/bin/llvm-ranlib
75 | sudo ln -s /usr/bin/llvm-objdump-15 /usr/bin/llvm-objdump
76 |
77 | - name: OS Setup (macOS)
78 | if: ${{ startsWith(matrix.metadata.os, 'macos-') }}
79 | run: |
80 | brew install wabt llvm@15
81 | [ -d "/opt/homebrew" ] && echo PATH="/opt/homebrew/opt/llvm@15/bin:$PATH" >> $GITHUB_ENV
82 | [ ! -d "/opt/homebrew" ] && echo PATH="/usr/local/opt/llvm@15/bin:$PATH" >> $GITHUB_ENV
83 | npm i -g wasm-opt pnpm concurrently
84 |
85 | - name: Tool Versions
86 | run: |
87 | echo clang
88 | clang -v
89 | echo '####################'
90 | echo llvm-ar
91 | llvm-ar -V
92 | echo '####################'
93 | echo llvm-nm
94 | llvm-nm -V
95 | echo '####################'
96 | echo llvm-ranlib
97 | llvm-ranlib -v
98 | echo '####################'
99 | echo wasm-opt
100 | wasm-opt --version
101 | echo '####################'
102 | echo wasm-strip
103 | wasm-strip --version
104 | echo '####################'
105 | echo python
106 | python3.12 -V
107 |
108 | - name: Install Rust
109 | uses: dtolnay/rust-toolchain@master
110 | with:
111 | toolchain: "1.76"
112 | components: "clippy,rustfmt"
113 |
114 | - name: Setup Wasmer
115 | if: ${{ matrix.metadata.target == 'wasix' }}
116 | uses: wasmerio/setup-wasmer@v3.1
117 |
118 | - name: Download wasix-libc
119 | if: ${{ matrix.metadata.target == 'wasix' }}
120 | uses: dsaltares/fetch-gh-release-asset@1.1.2
121 | with:
122 | repo: wasix-org/rust
123 | file: wasix-libc.tar.gz
124 | target: sysroot/wasix-libc.tar.gz
125 |
126 | - name: Unpack wasix-libc
127 | if: ${{ matrix.metadata.target == 'wasix' }}
128 | run: |
129 | cd sysroot
130 | tar xzf wasix-libc.tar.gz
131 |
132 | - name: Download wasix toolchain
133 | if: ${{ matrix.metadata.target == 'wasix' }}
134 | uses: dsaltares/fetch-gh-release-asset@1.1.2
135 | with:
136 | repo: wasix-org/rust
137 | file: ${{ matrix.metadata.wasix-toolchain-release-asset }}
138 | target: wasix-rust-toolchain/toolchain.tar.gz
139 |
140 | - name: Install wasix toolchain
141 | if: ${{ matrix.metadata.target == 'wasix' }}
142 | run: |
143 | cd wasix-rust-toolchain
144 | tar xzf toolchain.tar.gz
145 | chmod +x bin/*
146 | chmod +x lib/rustlib/*/bin/*
147 | chmod +x lib/rustlib/*/bin/gcc-ld/*
148 | rustup toolchain link wasix .
149 |
150 | - name: Build native
151 | if: ${{ matrix.metadata.target == 'native' }}
152 | run: cargo build --profile release-compact
153 |
154 | - name: Build wasix
155 | if: ${{ matrix.metadata.target == 'wasix' }}
156 | run: |
157 | export WASI_SYSROOT=$(pwd)/sysroot/wasix-libc/sysroot32
158 | bash build.sh
159 |
160 | - name: Archive build
161 | uses: actions/upload-artifact@v4
162 | with:
163 | name: ${{ matrix.metadata.artifact-name }}
164 | path: ${{ matrix.metadata.bin-path }}
165 |
166 | - name: build test suite JS app
167 | run: |
168 | cd test-suite/js-test-app
169 | pnpm i
170 | pnpm run build
171 |
172 | - name: Run API test suite (wasix)
173 | # note: we're counting on wasmer compiling and running WinterJS faster
174 | # that cargo builds the test-suite app. This may not be the case forever.
175 | if: ${{ matrix.metadata.target == 'wasix' }}
176 | run: |
177 | conc --kill-others --success "command-1" \
178 | "wasmer run . --net --mapdir /app:./test-suite/js-test-app/dist -- serve /app/bundle.js" \
179 | "sleep 10 && cd test-suite && cargo run"
180 | echo All tests are passing! 🎉
181 |
182 | - name: Run API test suite (native)
183 | # note: we're counting on wasmer compiling and running WinterJS faster
184 | # that cargo builds the test-suite app. This may not be the case forever.
185 | if: ${{ matrix.metadata.target == 'native' }}
186 | run: |
187 | conc --kill-others --success "command-1" \
188 | "./target/release-compact/winterjs serve ./test-suite/js-test-app/dist/bundle.js" \
189 | "sleep 10 && cd test-suite && cargo run"
190 | echo All tests are passing! 🎉
191 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 | build-and-test:
10 | uses: "./.github/workflows/build-and-test.yml"
11 |
12 | release:
13 | runs-on: ubuntu-latest
14 | needs: build-and-test
15 | steps:
16 | - name: Setup Wasmer
17 | uses: wasmerio/setup-wasmer@v3.1
18 |
19 | - name: Check out
20 | uses: actions/checkout@v3
21 | with:
22 | submodules: "recursive"
23 |
24 | - name: Download build artifacts
25 | uses: actions/download-artifact@v3
26 | with:
27 | path: build-artifacts
28 |
29 | - name: Publish
30 | run: |
31 | TAG_NAME=${{github.ref_name}}
32 | VERSION_NUMBER=${TAG_NAME#v}
33 | echo Publishing version $VERSION_NUMBER
34 |
35 | if ! grep -q "version = ['\"]$VERSION_NUMBER['\"]" wasmer.toml; then
36 | echo Tagged version must match version in wasmer.toml
37 | exit -1
38 | fi
39 |
40 | if ! grep -q "version = ['\"]$VERSION_NUMBER['\"]" Cargo.toml; then
41 | echo Tagged version must match version in Cargo.toml
42 | exit -1
43 | fi
44 |
45 | mkdir -p target/wasm32-wasmer-wasi/release
46 | mv build-artifacts/winterjs-wasix/winterjs.wasm target/wasm32-wasmer-wasi/release/winterjs.wasm
47 |
48 | wasmer publish --registry="wasmer.io" --token=${{ secrets.WAPM_PROD_TOKEN }} --non-interactive
49 |
50 | echo WinterJS version $VERSION_NUMBER was published successfully 🎉
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /doc
2 | **/target
3 | .direnv
4 | .envrc
5 | __pycache__
6 |
7 | *.pyc
8 | *.o
9 | *.so
10 | *.dll
11 | *.dylib
12 |
13 | .vscode
14 | .DS_Store
15 |
16 | **/.wrangler
17 |
18 | **/node_modules
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "winterjs"
3 | description = "The JavaScript runtime that brings JavaScript to Wasmer Edge."
4 | version = "1.1.5"
5 | repository = "https://github.com/wasmerio/winterjs/"
6 | license = "MIT"
7 | edition = "2021"
8 |
9 | [dependencies]
10 | lazy_static = "1.4.0"
11 | anyhow = "1.0.75"
12 | hyper = { version = "=0.14.28", features = [
13 | "server",
14 | "http1",
15 | "tcp",
16 | ], git = "https://github.com/wasix-org/hyper", branch = "v0.14.28" }
17 | tracing = "0.1.37"
18 | tracing-subscriber = { version = "0.3.17", features = ["env-filter", "fmt"] }
19 | serde_derive = "1.0.164"
20 | serde = "1.0.164"
21 | serde_json = "1.0.97"
22 | bytes = { version = "1.5.0", features = ["serde"] }
23 | once_cell = "1.18.0"
24 | rustls = { git = "https://github.com/wasix-org/rustls.git", branch = "v0.22.2", version = "=0.22.2" }
25 | hyper-rustls = { version = "=0.25.0", git = "https://github.com/wasix-org/hyper-rustls.git", branch = "v0.25.0" }
26 | h2 = { version = "=0.3.23", git = "https://github.com/wasix-org/h2.git", branch = "v0.3.23" }
27 | futures = "0.3.28"
28 | http = "0.2.9"
29 | form_urlencoded = "1.2.0"
30 |
31 | static-web-server = { git = "https://github.com/wasix-org/static-web-server", rev = "87bb6804a7e60db08399f8f7f22bb10bf028a489" }
32 |
33 | ion = { package = "ion", features = [
34 | "macros",
35 | ], git = "https://github.com/wasmerio/spiderfire.git" }
36 | ion-proc = { package = "ion-proc", git = "https://github.com/wasmerio/spiderfire.git" }
37 | runtime = { package = "runtime", git = "https://github.com/wasmerio/spiderfire.git", features = ["fetch"]}
38 | modules = { package = "modules", git = "https://github.com/wasmerio/spiderfire.git" }
39 | mozjs = { version = "0.14.1", git = "https://github.com/wasmerio/mozjs.git", branch = "wasi-gecko", features = [
40 | "streams",
41 | ] }
42 | mozjs_sys = { version = "0.68.2", git = "https://github.com/wasmerio/mozjs.git", branch = "wasi-gecko" }
43 |
44 | # for mozjs
45 | libc = { version = "=0.2.152", git = "https://github.com/wasix-org/libc.git", branch = "v0.2.152" }
46 |
47 | # libc = "=0.2.139"
48 |
49 | # NOTE: We need to pin and replace some dependencies to achieve wasix compatibility.
50 | tokio = { version = "=1.35.1", features = ["rt-multi-thread", "macros", "fs", "io-util", "signal"] }
51 | parking_lot = { version = "=0.12.1", features = ["nightly"] }
52 | url = "2.4.1"
53 | base64 = "0.21.4"
54 | async-trait = "0.1.74"
55 | uuid = { version = "1.5.0", features = ["v4"] }
56 | rand = "0.8.5"
57 | rand_core = "0.6.4"
58 | sha1 = "0.10.6"
59 | sha2 = "0.10.8"
60 | md5 = "0.7.0"
61 | clap = { version = "4.4.7", features = ["derive", "env"] }
62 | strum = { version = "0.25.0", features = ["derive"] }
63 | hmac = { version = "0.12.1", features = ["std"] }
64 | include_dir = "0.7.3"
65 | dyn-clone = "1.0.16"
66 | dyn-clonable = "0.9.0"
67 | self_cell = "1.0.3"
68 | glob-match = "0.2.1"
69 | sys-locale = "0.3.1"
70 |
71 | [target.'cfg(not(target_os = "wasi"))'.dependencies]
72 | ctrlc = "3.4.2"
73 |
74 | [patch.crates-io]
75 | hyper-rustls = { git = "https://github.com/wasix-org/hyper-rustls.git", branch = "v0.25.0" }
76 | socket2 = { git = "https://github.com/wasix-org/socket2.git", branch = "v0.5.5" }
77 | libc = { git = "https://github.com/wasix-org/libc.git", branch = "v0.2.152" }
78 | mio = { git = "https://github.com/wasix-org/mio.git", branch = "v0.8.9" }
79 | tokio = { git = "https://github.com/wasix-org/tokio.git", branch = "wasix-1.35.1" }
80 | rustls = { git = "https://github.com/wasix-org/rustls.git", branch = "v0.22.2" }
81 | hyper = { git = "https://github.com/wasix-org/hyper.git", branch = "v0.14.28" }
82 | h2 = { git = "https://github.com/wasix-org/h2", branch = "v0.3.23" }
83 | parking_lot_core = { git = "https://github.com/wasix-org/parking_lot.git", branch = "v0.12.1" }
84 |
85 | [profile.release-compact]
86 | inherits = "release"
87 | opt-level = "z"
88 | lto = true
89 | codegen-units = 1
90 | panic = "abort"
91 | strip = true
92 |
--------------------------------------------------------------------------------
/Cross.toml:
--------------------------------------------------------------------------------
1 | [target.aarch64-unknown-linux-gnu]
2 | image = "ghcr.io/servo/cross-aarch64-unknown-linux-gnu:main"
3 |
4 | [target.armv7-unknown-linux-gnueabihf]
5 | image = "ghcr.io/servo/cross-armv7-unknown-linux-gnueabihf:main"
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-present Wasmer, Inc. and its affiliates.
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 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
9 |
10 | WinterJS is *blazing-fast* JavaScript server that runs Service Workers scripts according to the [Winter Community Group specification](https://wintercg.org/).
11 |
12 | **WinterJS is able to handle up to 100,000 reqs/s in a single laptop** (see [Benchmark](./benchmark)).
13 |
14 | ----
15 |
16 | > Note: WinterJS is not officially endorsed by WinterCG, despite sharing "Winter" in their name. There are many [runtimes supporting WinterCG](https://runtime-keys.proposal.wintercg.org/), WinterJS being one among those.
17 |
18 | ## Running WinterJS with Wasmer
19 |
20 | The WinterJS server is published in Wasmer as [`wasmer/winterjs`](https://wasmer.io/wasmer/winterjs).
21 |
22 | You can run the HTTP server locally with:
23 |
24 | ```shell
25 | wasmer run wasmer/winterjs --net --mapdir=tests:tests tests/simple.js
26 | ```
27 |
28 | Where `simple.js` is:
29 |
30 | ```js
31 | addEventListener('fetch', (req) => {
32 | req.respondWith(new Response('hello'));
33 | });
34 | ```
35 |
36 | ## Building from source
37 |
38 | WinterJS needs to build SpiderMonkey from source as part of its own build process.
39 | Please follow the steps outlined here to make sure you are ready to build SpiderMonkey: https://github.com/wasmerio/mozjs/blob/master/README.md.
40 |
41 | You also need to do this before installing WinterJS with `cargo install`, which builds WinterJS from the source instead of downloading pre-built binaries.
42 |
43 | Also, when building WinterJS in debug mode, you need to have NodeJS and npm installed and run `npm install` beforehand.
44 | This is required since debug builds of WinterJS also build the TypeScript code in `src/builtins/internal_js_modules`.
45 | Release builds use the existing, pre-compiled `*.js` sources in that directory.
46 | This was done to simplify installing WinterJS with `cargo install`.
47 | If you update the TypeScript sources, make sure to update the `*.js` sources by building at least once in debug mode and commit the updated `*.js` files.
48 |
49 | Once you can build SpiderMonkey, you simply need to run `cargo build` as usual to build WinterJS itself.
50 |
51 | ## Running WinterJS Natively
52 |
53 | You can install WinterJS natively with:
54 |
55 | ```
56 | cargo install --git https://github.com/wasmerio/winterjs winterjs
57 | ```
58 |
59 | Once you have WinterJS installed, you can simply do:
60 |
61 | ```shell
62 | winterjs tests/simple.js
63 | ```
64 |
65 | And then access the server in https://localhost:8080/
66 |
67 | # How WinterJS works
68 |
69 | WinterJS is powered by [SpiderMonkey](https://spidermonkey.dev/), [Spiderfire](https://github.com/Redfire75369/spiderfire) and [hyper](https://hyper.rs/)
70 | to bring a new level of awesomeness to your Javascript apps.
71 |
72 | WinterJS is using the [WASIX](https://wasix.org) standard to compile to WebAssembly. Please note that compiling to WASIX is currently a complex process. We recommend using precompiled versions from [`wasmer/winterjs`](https://wasmer.io/wasmer/winterjs), but please open an issue if you need to compile to WASIX locally.
73 |
74 | ## Limitations
75 |
76 | WinterJS is fully compliant with the WinterCG spec, although the runtime itself is still a work in progress.
77 | For more information, see the API Compatibility section below.
78 |
79 | # WinterCG API Compatibility
80 |
81 | This section will be updated as APIs are added/fixed.
82 | If an API is missing from this section, that means that it is still not implemented.
83 |
84 | You can check a more detailed list here: https://runtime-compat.unjs.io/
85 |
86 | The following words are used to describe the status of an API:
87 |
88 | * ✅ Stable - The API is implemented and fully compliant with the spec. This does not account for potential undiscovered implementation errors in the native code.
89 | * 🔶 Partial - The API is implemented but not fully compliant with the spec and/or there are known limitations.
90 | * ❌ Pending - The API is not implemented yet.
91 |
92 | |API|Status|Notes|
93 | |:-:|:-:|:--|
94 | |`console`|✅ Stable|
95 | |`fetch`|✅ Stable|
96 | |`URL`|✅ Stable|
97 | |`URLSearchParams`|✅ Stable|
98 | |`Request`|✅ Stable|
99 | |`Headers`|✅ Stable|
100 | |`Response`|✅ Stable|
101 | |`Blob`|✅ Stable|
102 | |`File`|✅ Stable|
103 | |`FormData`|✅ Stable|
104 | |`TextDecoder`|✅ Stable|
105 | |`TextDecoderStream`|✅ Stable|
106 | |`TextEncoder`|✅ Stable|
107 | |`TextEncoderStream`|✅ Stable|
108 | |`ReadableStream` and supporting types|✅ Stable|
109 | |`WritableStream` and supporting types|✅ Stable|
110 | |`TransformStream` and supporting types|🔶 Partial|Back-pressure is not implemented
111 | |`atob`|✅ Stable|
112 | |`btoa`|✅ Stable|
113 | |`performance.now()`|✅ Stable|
114 | |`performance.timeOrigin`|✅ Stable|
115 | |`crypto`|✅ Stable|
116 | |`crypto.subtle`|🔶 Partial|Only HMAC, MD5 and SHA algorithms are supported
117 |
118 | # Other supported APIs
119 |
120 | The following (non-WinterCG) APIs are implemented and accessible in WinterJS:
121 |
122 | |API|Status|Notes|
123 | |:-:|:-:|:--|
124 | |[Service Workers Caches API](https://www.w3.org/TR/service-workers/#cache-objects)|✅ Stable|Accessible via `caches`. `caches.default` (similar to [Cloudflare workers](https://developers.cloudflare.com/workers/runtime-apis/cache/#accessing-cache)) is also available.
The current implementation is memory-backed, and cached responses will *not* persist between multiple runs of WinterJS.
125 |
--------------------------------------------------------------------------------
/assets/winterjs-logo-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wasmerio/winterjs/b74e8c70f8106c6be790cb33dea5409d89d42b37/assets/winterjs-logo-black.png
--------------------------------------------------------------------------------
/assets/winterjs-logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wasmerio/winterjs/b74e8c70f8106c6be790cb33dea5409d89d42b37/assets/winterjs-logo-white.png
--------------------------------------------------------------------------------
/benchmark/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .wrangler
3 |
--------------------------------------------------------------------------------
/benchmark/README.md:
--------------------------------------------------------------------------------
1 | # Benchmarking
2 |
3 | This benchmarks are done in a MacBook Pro M3 Max laptop with 64 GB of RAM, on Feb 28th, 2024.
4 |
5 | This benchmark compares:
6 | * [`workerd`](#workerd): Cloudflare's Service Worker server powered by V8 (repo: https://github.com/cloudflare/workerd)
7 | * [WinterJS Native](#winterjs-native): WinterJS running natively
8 | * [Bun](#bun): Bun (basic http server replicating similar behavior)
9 | * [Node](#node): Node (basic http server replicating similar behavior)
10 | * [WinterJS WASIX](#winterjs-wasix): WinterJS running in Wasmer via WASIX
11 | * [`wrangler`](#wrangler): Cloudflare's Service Worker powered by Node (repo: https://github.com/cloudflare/workers-sdk)
12 |
13 |
14 | > Note: this benchmarks focuses on running a simple workload [`simple.js`](./simple.js). There's also the [`complex.js`](./complex.js) file, which does Server Side Rendering using React.
15 |
16 |
17 | ## Workerd
18 |
19 | Using latest release binary: https://github.com/cloudflare/workerd/releases/tag/v1.20231030.0
20 |
21 | Running the server:
22 |
23 | ```
24 | $ ./workerd-darwin-arm64 serve ./worker.capnp
25 | ```
26 |
27 | And then:
28 |
29 | ```bash
30 | $ wrk -t12 -c400 -d10s http://127.0.0.1:8080
31 | Running 10s test @ http://127.0.0.1:8080
32 | 12 threads and 400 connections
33 | Thread Stats Avg Stdev Max +/- Stdev
34 | Latency 14.55ms 22.15ms 116.50ms 81.86%
35 | Req/Sec 3.32k 1.52k 9.65k 69.58%
36 | 396904 requests in 10.04s, 31.42MB read
37 | Socket errors: connect 155, read 110, write 0, timeout 0
38 | Requests/sec: 39522.93
39 | Transfer/sec: 3.13MB
40 | ```
41 |
42 | ## WinterJS (Native)
43 |
44 | Running the server:
45 |
46 | ```
47 | $ cargo run --release -- ./simple.js
48 | ```
49 |
50 | Benchmarking:
51 | ```
52 | $ wrk -t12 -c400 -d10s http://127.0.0.1:8080
53 | Running 10s test @ http://127.0.0.1:8080
54 | 12 threads and 400 connections
55 | Thread Stats Avg Stdev Max +/- Stdev
56 | Latency 11.70ms 89.98ms 1.52s 98.29%
57 | Req/Sec 12.66k 6.49k 46.35k 58.80%
58 | 1517019 requests in 10.10s, 173.61MB read
59 | Socket errors: connect 155, read 108, write 0, timeout 29
60 | Requests/sec: 150175.14
61 | Transfer/sec: 17.19MB
62 | ```
63 |
64 |
65 | ## Bun
66 |
67 | > Note: Bun does run another equivalent script to `simple.js` (`bun-simple.js`), since Bun does not support WinterCG natively.
68 |
69 | Running the server:
70 |
71 | ```
72 | $ bun ./bun-simple.js
73 | ```
74 |
75 | Benchmarking:
76 |
77 | ```
78 | $ wrk -t12 -c400 -d10s http://127.0.0.1:8080
79 | Running 10s test @ http://127.0.0.1:8080
80 | 12 threads and 400 connections
81 | Thread Stats Avg Stdev Max +/- Stdev
82 | Latency 2.05ms 401.85us 8.29ms 78.81%
83 | Req/Sec 9.83k 5.40k 17.65k 45.96%
84 | 1186158 requests in 10.10s, 135.75MB read
85 | Socket errors: connect 155, read 57, write 0, timeout 0
86 | Requests/sec: 117418.44
87 | Transfer/sec: 13.44MB
88 | ```
89 |
90 |
91 | ## Node
92 |
93 | > Note: Node does run another equivalent script to `simple.js` (`node-simple.js`), since Node does not support WinterCG natively.
94 |
95 | Running the server:
96 |
97 | ```
98 | $ node ./node-simple.js
99 | ```
100 |
101 | Benchmarking:
102 |
103 | ```
104 | $ wrk -t12 -c400 -d10s http://127.0.0.1:8080
105 | Running 10s test @ http://127.0.0.1:8080
106 | 12 threads and 400 connections
107 | Thread Stats Avg Stdev Max +/- Stdev
108 | Latency 3.91ms 10.03ms 294.68ms 99.16%
109 | Req/Sec 6.25k 2.00k 11.33k 73.92%
110 | 747990 requests in 10.02s, 122.69MB read
111 | Socket errors: connect 155, read 306, write 0, timeout 0
112 | Requests/sec: 74615.22
113 | Transfer/sec: 12.24MB
114 | ```
115 |
116 | ## WinterJS (WASIX)
117 |
118 | Running the server:
119 |
120 | ```
121 | $ wasmer run wasmer/winterjs --mapdir=/app:. --net -- /app/simple.js
122 | ```
123 |
124 | Benchmarking:
125 |
126 | ```
127 | $ wrk -t12 -c400 -d10s http://127.0.0.1:8080
128 | Running 10s test @ http://127.0.0.1:8080
129 | 12 threads and 400 connections
130 | Thread Stats Avg Stdev Max +/- Stdev
131 | Latency 11.22ms 8.97ms 168.70ms 87.08%
132 | Req/Sec 1.05k 526.90 2.99k 73.00%
133 | 125542 requests in 10.03s, 14.37MB read
134 | Socket errors: connect 155, read 271, write 0, timeout 0
135 | Requests/sec: 12519.78
136 | Transfer/sec: 1.43MB
137 | ```
138 |
139 | ## Wrangler
140 |
141 | Running the server:
142 |
143 | ```bash
144 | $ npx wrangler@3.15.0 dev ./simple.js
145 | ```
146 |
147 | Benchmarking (please note that the port is in `8787`):
148 |
149 | ```bash
150 | $ wrk -t12 -c400 -d10s http://127.0.0.1:8787
151 | Running 10s test @ http://127.0.0.1:8787
152 | 12 threads and 400 connections
153 | Thread Stats Avg Stdev Max +/- Stdev
154 | Latency 94.97ms 118.83ms 401.80ms 83.00%
155 | Req/Sec 166.86 189.25 810.00 77.68%
156 | 19461 requests in 10.08s, 1.56MB read
157 | Socket errors: connect 155, read 259, write 1, timeout 0
158 | Requests/sec: 1930.96
159 | Transfer/sec: 158.63KB
160 | ```
161 |
--------------------------------------------------------------------------------
/benchmark/bun-simple.js:
--------------------------------------------------------------------------------
1 | const server = Bun.serve({
2 | port: 8080,
3 | fetch(request) {
4 | return new Response('hello');
5 | },
6 | });
7 |
8 | console.log(`Server running at ${server.url}`);
9 |
--------------------------------------------------------------------------------
/benchmark/node-simple.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | const server = http.createServer((req, res) => {
4 | res.writeHead(200, {'Content-Type': 'text/plain'});
5 | res.end('hello');
6 | });
7 |
8 | server.listen(8080, () => {
9 | console.log('Server running at http://localhost:8080/');
10 | });
11 |
--------------------------------------------------------------------------------
/benchmark/simple.js:
--------------------------------------------------------------------------------
1 |
2 | addEventListener('fetch', (req) => {
3 | req.respondWith(new Response('hello'));
4 | });
5 |
--------------------------------------------------------------------------------
/benchmark/worker.capnp:
--------------------------------------------------------------------------------
1 | using Workerd = import "/workerd/workerd.capnp";
2 |
3 | const config :Workerd.Config = (
4 | services = [
5 | (name = "main", worker = .mainWorker),
6 | ],
7 |
8 | sockets = [
9 | # Serve HTTP on port 8080.
10 | ( name = "http",
11 | address = "*:8080",
12 | http = (),
13 | service = "main"
14 | ),
15 | ]
16 | );
17 |
18 | const mainWorker :Workerd.Worker = (
19 | serviceWorkerScript = embed "./simple.js",
20 | compatibilityDate = "2023-02-28",
21 | # Learn more about compatibility dates at:
22 | # https://developers.cloudflare.com/workers/platform/compatibility-dates/
23 | );
24 |
--------------------------------------------------------------------------------
/build-debug.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | set -euo pipefail
4 | set -x
5 |
6 | # Note: cargo-wasix automatically runs wasm-opt with -O2, which makes the resulting binary unusable.
7 | # Instead, we use the toolchain to build (cargo +wasix instead of cargo wasix) and optimize manually.
8 | cargo +wasix build --target wasm32-wasmer-wasi
9 | mv target/wasm32-wasmer-wasi/debug/winterjs.wasm x.wasm
10 | wasm-opt x.wasm -o target/wasm32-wasmer-wasi/debug/winterjs.wasm -O1 --enable-bulk-memory --enable-threads --enable-reference-types --no-validation --asyncify
11 | rm x.wasm
12 | wasm-strip target/wasm32-wasmer-wasi/debug/winterjs.wasm
--------------------------------------------------------------------------------
/build-single-threaded.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | set -euo pipefail
4 | set -x
5 |
6 | # Note: cargo-wasix automatically runs wasm-opt with -O2, which makes the resulting binary unusable.
7 | # Instead, we use the toolchain to build (cargo +wasix instead of cargo wasix) and optimize manually.
8 | cargo +wasix build --target wasm32-wasmer-wasi -r
9 | mv target/wasm32-wasmer-wasi/release/winterjs.wasm x.wasm
10 | # In single-thread-only builds, we skip --asyncify
11 | wasm-opt x.wasm -o target/wasm32-wasmer-wasi/release/winterjs-st.wasm -O1 --enable-bulk-memory --enable-reference-types --no-validation
12 | rm x.wasm
13 | wasm-strip target/wasm32-wasmer-wasi/release/winterjs-st.wasm
--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
1 | use std::process::Command;
2 |
3 | fn main() {
4 | // We want to let people install WinterJS from source, so we can't have
5 | // a dependency on TSC at all times. The assumption here is that whoever
6 | // updates TS sources for builtin modules will at least run WinterJS
7 | // once in debug mode, and the JS scripts will be updated and pushed.
8 | let profile = std::env::var("PROFILE").unwrap();
9 | if profile == "debug" {
10 | let builtins_dir = std::env::current_dir().unwrap().join("src/builtins");
11 |
12 | let dir = builtins_dir.join("internal_js_modules");
13 | assert!(Command::new("npx")
14 | .arg("tsc")
15 | .current_dir(dir)
16 | .output()
17 | .unwrap()
18 | .status
19 | .success());
20 |
21 | let dir = builtins_dir.join("js_globals");
22 | assert!(Command::new("npx")
23 | .arg("tsc")
24 | .current_dir(dir)
25 | .output()
26 | .unwrap()
27 | .status
28 | .success());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | set -euo pipefail
4 | set -x
5 |
6 | # Note: cargo-wasix automatically runs wasm-opt with -O2, which makes the resulting binary unusable.
7 | # Instead, we use the toolchain to build (cargo +wasix instead of cargo wasix) and optimize manually.
8 | cargo +wasix build --target wasm32-wasmer-wasi -r
9 | mv target/wasm32-wasmer-wasi/release/winterjs.wasm x.wasm
10 | wasm-opt x.wasm -o target/wasm32-wasmer-wasi/release/winterjs.wasm -O1 --enable-bulk-memory --enable-threads --enable-reference-types --no-validation --asyncify
11 | rm x.wasm
12 | wasm-strip target/wasm32-wasmer-wasi/release/winterjs.wasm
--------------------------------------------------------------------------------
/docs/References.md:
--------------------------------------------------------------------------------
1 | # References
2 |
3 | Links to related resources.
4 |
5 |
6 | * Winter CG:
7 | Community group defining standards for server-side Javascript
8 | https://wintercg.org/work
9 | - [Minimum API](https://common-min-api.proposal.wintercg.org/)
10 | - [Web Crypto Streams](https://webcrypto-streams.proposal.wintercg.org/)
11 | - [Performance](https://github.com/wintercg/performance)
12 | (like `performance.now()`)
13 | - [fetch](https://fetch.spec.wintercg.org/)
14 |
15 |
16 | ## Spidermonkey
17 |
18 | * [Cookbook examples](https://github.com/mozilla-spidermonkey/spidermonkey-embedding-examples)
19 | Examples for common spidermonkey operations.
20 |
--------------------------------------------------------------------------------
/examples/echo/app.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: wasmer.io/App.v0
3 | name: wasmer-winter-js-echo
4 | package: wasmer-examples/winter-js-echo
5 |
--------------------------------------------------------------------------------
/examples/echo/wasmer.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = 'wasmer-examples/winter-js-echo'
3 | version = '0.2.0'
4 | description = 'Javascript echo server using winter-js.'
5 |
6 | # See more keys and definitions at https://docs.wasmer.io/registry/manifest
7 |
8 | [dependencies]
9 | "wasmer/winterjs" = "*"
10 |
11 | [fs]
12 | "/app" = "./app"
13 |
14 | [[command]]
15 | name = "script"
16 | module = "wasmer/winterjs:winterjs"
17 | runner = "https://webc.org/runner/wasi"
18 |
19 | [command.annotations.wasi]
20 | main-args = ["serve", "/app/app.js", "--script"]
--------------------------------------------------------------------------------
/examples/hello-world.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | function handler(req) {
4 | return new Response('Hello, world!', {
5 | headers: {
6 | 'content-type': 'text/plain',
7 | },
8 | });
9 | }
10 |
11 | addEventListener('fetch', (ev) => ev.respondWith(handler(ev.request)));
12 |
--------------------------------------------------------------------------------
/examples/http-echo/README.md:
--------------------------------------------------------------------------------
1 | # http-echo-plain
2 |
3 | A simple http echo server that responds with information about the incoming
4 | request.
5 |
6 | The response format can be customized with:
7 |
8 | * The `Accept` header (`application/json` or `text/html`)
9 | * The `?format` query param
10 | - `json`
11 | - `html`
12 | - `echo` (returns request headers and body unmodified)
13 |
14 | ## Running locally
15 |
16 | ```bash
17 | wasmer run wasmer/winterjs --net --mapdir=src:src -- src/index.js --watch
18 | ```
19 |
20 | This example is also deployed to Wasmer Edge at: https://httpinfo-winterjs.wasmer.app .
21 |
--------------------------------------------------------------------------------
/examples/http-echo/app.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: wasmer.io/App.v0
3 | name: httpinfo-winterjs
4 | package: wasmer-examples/http-echo-winterjs@0.1.0
5 | debug: false
6 | app_id: da_p2qMIbt8UvX2
7 |
--------------------------------------------------------------------------------
/examples/http-echo/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | async function handleRequest(req) {
3 | const accept = req.headers.get('accept') ?? '';
4 | const url = new URL(req.url);
5 | const queryFormat = url.searchParams.get('format');
6 |
7 | let outputFormat = 'html';
8 |
9 | if (queryFormat) {
10 | switch (queryFormat) {
11 | case 'json':
12 | outputFormat = 'json';
13 | break;
14 | case 'html':
15 | outputFormat = 'html';
16 | break;
17 | case 'echo':
18 | outputFormat = 'echo';
19 | break;
20 | }
21 | } else if (accept) {
22 | if (accept.startsWith('application/json')) {
23 | outputFormat = 'json';
24 | } else if (accept.search('text/html') !== -1) {
25 | outputFormat = 'html';
26 | }
27 | }
28 |
29 | switch (outputFormat) {
30 | case 'json':
31 | return buildResponseJson(req);
32 | case 'html':
33 | return buildResponseHtml(req);
34 | case 'echo':
35 | return buildResponseEcho(req);
36 | }
37 | }
38 |
39 | async function requestBodyToString(req) {
40 | try {
41 | const body = await req.text();
42 | return !body ? '' : body;
43 | } catch (e) {
44 | console.warn("Could not decode request body", e);
45 | return "";
46 | }
47 | }
48 |
49 |
50 | addEventListener("fetch", (ev) => {
51 | ev.respondWith(handleRequest(ev.request));
52 | });
53 |
54 | async function buildResponseJson(req) {
55 | const reqBody = await requestBodyToString(req);
56 |
57 | const data = {
58 | url: req.url,
59 | method: req.method,
60 | headers: Object.fromEntries(req.headers),
61 | body: reqBody,
62 | };
63 | const body = JSON.stringify(data, null, 2);
64 | return new Response(body, {
65 | headers: { "content-type": "application/json" },
66 | });
67 | }
68 |
69 | async function buildResponseHtml(req) {
70 |
71 | let headers = '';
72 |
73 | for (const [key, value] of req.headers.entries()) {
74 | headers += `
75 |
76 | ${key} |
77 | ${value} |
78 |
`;
79 | }
80 |
81 | const url = new URL(req.url);
82 | url.searchParams.set('format', 'json');
83 | const jsonUrl = url.pathname + url.search;
84 |
85 | const reqBody = await requestBodyToString(req);
86 |
87 | let html = `
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | HTTP-Info - Analyze HTTP requests
96 |
97 |
98 |
99 |
100 |
101 |
102 | HTTP-Info
103 |
104 |
105 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | URL |
116 | ${req.url} |
117 |
118 |
119 | Method |
120 | ${req.method} |
121 |
122 |
123 | Headers |
124 |
125 |
126 |
127 | ${headers}
128 |
129 |
130 | |
131 |
132 | Body |
133 | ${reqBody} |
134 |
135 |
136 |
137 |
138 |
139 |
142 |
143 |
This service provides information about the incoming HTTP request.
144 | It is useful for debugging and analyzing HTTP clients.
145 |
146 |
You can control the output format by:
147 |
148 |
149 | - Setting the
Accept
header to application/json
or text/html
150 | -
151 | Setting the
?format=XXX
query parameter to
152 | json
, html
or echo
.
153 |
154 |
155 |
156 | By default the output format is html
.
157 | If the format is echo
, the response will contain
158 | the request headers and body unchanged.
159 |
160 |
161 |
162 |
163 |
164 |
165 |
168 |
169 | This site is hosted on
Wasmer Edge.
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 | `;
178 |
179 | return new Response(html, {
180 | headers: { "content-type": "text/html" },
181 | });
182 | }
183 |
184 | function buildResponseEcho(req) {
185 | return new Response(req.body, {
186 | headers: req.headers,
187 | });
188 | }
189 |
--------------------------------------------------------------------------------
/examples/http-echo/wasmer.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wasmer-examples/http-echo-winterjs"
3 | version = "0.1.0"
4 | description = "A simple HTTP echo server using WinterJS - responds with request information."
5 |
6 | [dependencies]
7 | "wasmer/winterjs" = "*"
8 |
9 | [fs]
10 | "/src" = "./src"
11 |
12 | [[command]]
13 | name = "script"
14 | module = "wasmer/winterjs:winterjs"
15 | runner = "https://webc.org/runner/wasi"
16 |
17 | [command.annotations.wasi]
18 | main-args = ["serve", "/src/index.js", "--script"]
19 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flakeutils": {
4 | "inputs": {
5 | "systems": "systems"
6 | },
7 | "locked": {
8 | "lastModified": 1701680307,
9 | "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
10 | "owner": "numtide",
11 | "repo": "flake-utils",
12 | "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "numtide",
17 | "repo": "flake-utils",
18 | "type": "github"
19 | }
20 | },
21 | "nixpkgs": {
22 | "locked": {
23 | "lastModified": 1702938738,
24 | "narHash": "sha256-O7Vb0xC9s4Dmgxj8APEpuuMj7HsLgPbpy1UKvNVJp7o=",
25 | "owner": "NixOS",
26 | "repo": "nixpkgs",
27 | "rev": "dd8e82f3b4017b8faa52c2b1897a38d53c3c26cb",
28 | "type": "github"
29 | },
30 | "original": {
31 | "id": "nixpkgs",
32 | "type": "indirect"
33 | }
34 | },
35 | "root": {
36 | "inputs": {
37 | "flakeutils": "flakeutils",
38 | "nixpkgs": "nixpkgs"
39 | }
40 | },
41 | "systems": {
42 | "locked": {
43 | "lastModified": 1681028828,
44 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
45 | "owner": "nix-systems",
46 | "repo": "default",
47 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
48 | "type": "github"
49 | },
50 | "original": {
51 | "owner": "nix-systems",
52 | "repo": "default",
53 | "type": "github"
54 | }
55 | }
56 | },
57 | "root": "root",
58 | "version": 7
59 | }
60 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "winterjs - A Javascript runtime following the WinterCG spec.";
3 |
4 | inputs = {
5 | flakeutils = {
6 | url = "github:numtide/flake-utils";
7 | inputs.nixpkgs.follows = "nixpkgs";
8 | };
9 | };
10 |
11 | outputs = { self, nixpkgs, flakeutils }:
12 | flakeutils.lib.eachDefaultSystem (system:
13 | let
14 | NAME = "winterjs";
15 | VERSION = "0.1.0";
16 |
17 | pkgs = import nixpkgs {
18 | inherit system;
19 | };
20 |
21 | llvm = pkgs.llvmPackages_latest;
22 | in
23 | rec {
24 |
25 | # packages.${NAME} = pkgs.stdenv.mkDerivation {
26 | # pname = NAME;
27 | # version = VERSION;
28 |
29 | # buildPhase = "echo 'no-build'";
30 | # };
31 |
32 | # defaultPackage = packages.${NAME};
33 |
34 | # For `nix run`.
35 | # apps.${NAME} = flakeutils.lib.mkApp {
36 | # drv = packages.${NAME};
37 | # };
38 | # defaultApp = apps.${NAME};
39 |
40 | devShell = pkgs.mkShell.override { stdenv = pkgs.llvmPackages_latest.stdenv; } rec {
41 | packages = with pkgs; [
42 | # llvmPackages_16.bintools-unwrapped
43 | pkg-config
44 | gnum4
45 |
46 | # builder
47 | gnumake
48 | cmake
49 | bear
50 |
51 | # debugger
52 | llvmPackages_latest.lldb
53 | gdb
54 |
55 | # fix headers not found
56 | clang-tools
57 |
58 | # LSP and compiler
59 | llvmPackages_latest.libstdcxxClang
60 |
61 | # other tools
62 | cppcheck
63 | llvmPackages_latest.libllvm
64 | valgrind
65 |
66 | # stdlib for cpp
67 | llvmPackages_latest.libcxx
68 |
69 | # libs
70 | llvmPackages_latest.libclang
71 | zlib
72 | ];
73 |
74 | LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath packages;
75 |
76 |
77 | shellHook = ''
78 | export AS="$CC -c"
79 | '';
80 | };
81 |
82 | # devShell = pkgs.stdenv.mkDerivation {
83 | # name = NAME;
84 | # src = self;
85 | # buildInputs = with pkgs; [
86 | # ];
87 | # runtimeDependencies = with pkgs; [ ];
88 |
89 | # LD_LIBRARY_PATH= pkgs.lib.makeLibraryPath (with pkgs; [
90 | # zlib
91 | # llvmPackages_16.libclang
92 | # ]);
93 |
94 | # # standalone as(1) doesn’t treat -DNDEBUG as -D NDEBUG (define), but rather -D (produce
95 | # # assembler debugging messages) + -N (invalid option); see also
96 | # # /nix/store/a64w6zy8w9hcj6b4g5nz0dl6zyd24c1x-gcc-wrapper-11.3.0/bin/as: invalid option -- 'N'
97 | # # make[4]: *** [/path/to/mozjs/mozjs/mozjs/config/rules.mk:664: icu_data.o] Error 1
98 | # # make[3]: *** [/path/to/mozjs/mozjs/mozjs/config/recurse.mk:72: config/external/icu/data/target-objects] Error 2
99 | # AS="$CC -c";
100 | # };
101 | }
102 | );
103 | }
104 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "winterjs",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "devDependencies": {
8 | "typescript": "^5.3.3"
9 | }
10 | },
11 | "node_modules/typescript": {
12 | "version": "5.3.3",
13 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
14 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
15 | "dev": true,
16 | "bin": {
17 | "tsc": "bin/tsc",
18 | "tsserver": "bin/tsserver"
19 | },
20 | "engines": {
21 | "node": ">=14.17"
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "typescript": "^5.3.3"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/rust-toolchain:
--------------------------------------------------------------------------------
1 | 1.76
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {} }:
2 | pkgs.clangStdenv.mkDerivation {
3 | name = "mozjs-shell";
4 |
5 | shellHook = ''
6 | export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath [
7 | pkgs.zlib
8 | pkgs.libclang
9 | ]}
10 |
11 | # standalone as(1) doesn’t treat -DNDEBUG as -D NDEBUG (define), but rather -D (produce
12 | # assembler debugging messages) + -N (invalid option); see also
13 | # /nix/store/a64w6zy8w9hcj6b4g5nz0dl6zyd24c1x-gcc-wrapper-11.3.0/bin/as: invalid option -- 'N'
14 | # make[4]: *** [/path/to/mozjs/mozjs/mozjs/config/rules.mk:664: icu_data.o] Error 1
15 | # make[3]: *** [/path/to/mozjs/mozjs/mozjs/config/recurse.mk:72: config/external/icu/data/target-objects] Error 2
16 | export AS="$CC -c"
17 | '';
18 |
19 | buildInputs = [
20 | pkgs.rustup
21 | pkgs.python3
22 | pkgs.perl
23 |
24 | pkgs.llvmPackages.bintools-unwrapped
25 | pkgs.pkg-config
26 | pkgs.gnum4
27 |
28 | pkgs.zlib
29 | pkgs.libclang
30 | ];
31 | }
32 |
--------------------------------------------------------------------------------
/src/builtins/cache/cache_storage.rs:
--------------------------------------------------------------------------------
1 | use std::{cell::RefCell, rc::Rc};
2 |
3 | use ion::{
4 | class::Reflector,
5 | conversions::ToValue,
6 | function::Opt,
7 | string::byte::{ByteString, VerbatimBytes},
8 | ClassDefinition, Context, Heap, Object, Promise, Result, Value,
9 | };
10 | use lazy_static::lazy_static;
11 | use mozjs_sys::jsapi::JSObject;
12 | use runtime::globals::fetch::RequestInfo;
13 |
14 | use crate::{ion_err, ion_mk_err};
15 |
16 | use super::{Cache, CacheQueryOptions};
17 |
18 | lazy_static! {
19 | static ref DEFAULT_CACHE_KEY: ByteString =
20 | ByteString::from("_____WINTERJS_DEFAULT_CACHE_____".to_string().into())
21 | .expect("Should be able to create default cache key");
22 | }
23 |
24 | #[derive(FromValue)]
25 | pub struct MultiCacheQueryOptions {
26 | ignore_search: Option,
27 | ignore_method: Option,
28 | ignore_vary: Option,
29 | cache_name: Option>,
30 | }
31 |
32 | // (Request, Response)
33 | pub(super) type CacheEntryList = Vec<(Heap<*mut JSObject>, Heap<*mut JSObject>)>;
34 |
35 | #[js_class]
36 | pub struct CacheStorage {
37 | reflector: Reflector,
38 |
39 | // Note: The order of the caches is important, so we can't naively use a hashmap here
40 | #[trace(no_trace)]
41 | pub(super) caches: Vec<(ByteString, Rc>)>,
42 | }
43 |
44 | #[js_class]
45 | impl CacheStorage {
46 | #[ion(constructor)]
47 | pub fn constructor() -> Result {
48 | ion_err!("Cannot construct this type", Type)
49 | }
50 |
51 | #[ion(name = "match")]
52 | pub fn r#match(
53 | &self,
54 | cx: &Context,
55 | key: RequestInfo,
56 | Opt(options): Opt,
57 | ) -> Promise {
58 | let cache_name = options.as_ref().and_then(|o| o.cache_name.as_ref());
59 | let query_options = options
60 | .as_ref()
61 | .map(|o| CacheQueryOptions {
62 | ignore_method: o.ignore_method,
63 | ignore_search: o.ignore_search,
64 | ignore_vary: o.ignore_vary,
65 | })
66 | .unwrap_or_default();
67 | for c in &self.caches {
68 | if let Some(cache_name) = cache_name {
69 | if &c.0 != cache_name {
70 | continue;
71 | }
72 | }
73 |
74 | let responses = match Cache::match_all_impl(
75 | c.1.try_borrow().expect("Should be able to borrow entries"),
76 | cx,
77 | Some(key.clone()),
78 | 1,
79 | &query_options,
80 | ) {
81 | Ok(r) => r,
82 | Err(e) => return Promise::rejected(cx, e),
83 | };
84 |
85 | if !responses.is_empty() {
86 | return Promise::resolved(cx, responses[0]);
87 | }
88 | }
89 |
90 | Promise::resolved(cx, Value::undefined(cx))
91 | }
92 |
93 | pub fn has(&self, cx: &Context, key: ByteString) -> Promise {
94 | Promise::resolved(cx, self.caches.iter().any(|c| c.0 == key))
95 | }
96 |
97 | pub fn open(&mut self, cx: &Context, key: ByteString) -> Promise {
98 | let index =
99 | match self
100 | .caches
101 | .iter()
102 | .enumerate()
103 | .find_map(|(i, c)| if c.0 == key { Some(i) } else { None })
104 | {
105 | Some(i) => i,
106 | None => {
107 | self.caches.push((key, Rc::new(RefCell::new(vec![]))));
108 | self.caches.len() - 1
109 | }
110 | };
111 |
112 | let cache = Cache::new_object(cx, Box::new(Cache::new(self.caches[index].1.clone())));
113 | Promise::resolved(cx, cache)
114 | }
115 |
116 | pub fn delete(&mut self, cx: &Context, key: ByteString) -> Promise {
117 | if key == *DEFAULT_CACHE_KEY {
118 | return Promise::rejected(cx, ion_mk_err!("Cannot delete the default cache", Type));
119 | }
120 |
121 | let index = self
122 | .caches
123 | .iter()
124 | .enumerate()
125 | .find(|c| c.1 .0 == key)
126 | .map(|c| c.0);
127 | if let Some(index) = index {
128 | self.caches.remove(index);
129 | Promise::resolved(cx, true)
130 | } else {
131 | Promise::resolved(cx, false)
132 | }
133 | }
134 |
135 | pub fn keys(&self, cx: &Context) -> Promise {
136 | let result = self.caches.iter().map(|c| c.0.clone()).collect::>();
137 | Promise::resolved(cx, result)
138 | }
139 |
140 | #[ion(get)]
141 | pub fn get_default(&self, cx: &Context) -> *mut JSObject {
142 | assert!(!self.caches.is_empty() && self.caches[0].0 == *DEFAULT_CACHE_KEY);
143 | Cache::new_object(cx, Box::new(Cache::new(self.caches[0].1.clone())))
144 | }
145 | }
146 |
147 | pub fn define(cx: &Context, global: &Object) -> bool {
148 | if !CacheStorage::init_class(cx, global).0 {
149 | return false;
150 | }
151 |
152 | let mut caches = CacheStorage {
153 | reflector: Default::default(),
154 | caches: Default::default(),
155 | };
156 |
157 | caches
158 | .caches
159 | .push((DEFAULT_CACHE_KEY.clone(), Rc::new(RefCell::new(vec![]))));
160 |
161 | let caches_obj = CacheStorage::new_object(cx, Box::new(caches));
162 | global.set(
163 | cx,
164 | "caches",
165 | &Object::from(cx.root(caches_obj)).as_value(cx),
166 | )
167 | }
168 |
--------------------------------------------------------------------------------
/src/builtins/core/core.js:
--------------------------------------------------------------------------------
1 | export const getPromiseState = ________winterjs_core_Internal______.getPromiseState;
2 | export const setPromiseHooks = ________winterjs_core_Internal______.setPromiseHooks;
3 |
4 | export default Object.freeze(________winterjs_core_Internal______);
5 |
--------------------------------------------------------------------------------
/src/builtins/core/mod.rs:
--------------------------------------------------------------------------------
1 | use std::{cell::RefCell, os::raw::c_void};
2 |
3 | use ion::{function_spec, Context, Function, Local, Object, PermanentHeap, Promise, Result, Value};
4 | use mozjs::{
5 | glue::{CreatePromiseLifecycleCallbacks, PromiseLifecycleTraps},
6 | jsapi::{Handle, SetPromiseLifecycleCallbacks},
7 | };
8 | use mozjs_sys::jsapi::{JSContext, JSFunction, JSFunctionSpec, JSObject};
9 | use runtime::module::NativeModule;
10 |
11 | use crate::ion_mk_err;
12 |
13 | thread_local! {
14 | static CALLBACKS_REGISTERED: RefCell = RefCell::new(false);
15 | static INIT: RefCell