@rust-js/resources 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builtins/Cargo.toml +20 -0
- package/builtins/build.sh +20 -0
- package/builtins/src/lib.rs +264 -0
- package/package.json +32 -0
- package/react/Cargo.toml +27 -0
- package/react/build.rs +59 -0
- package/react/build.sh +44 -0
- package/react/cfg.js +60 -0
- package/react/src/dom.rs +542 -0
- package/react/src/elements.rs +8556 -0
- package/react/src/event.rs +238 -0
- package/react/src/lib.rs +1065 -0
- package/react/versions.json +2103 -0
- package/rust-toolchain.toml +4 -0
- package/serde/Cargo.lock +107 -0
- package/serde/Cargo.toml +15 -0
- package/serde/src/lib.rs +1 -0
- package/webapi/Cargo.toml +23 -0
- package/webapi/build.sh +32 -0
- package/webapi/src/lib.rs +6822 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# The js crate as a Cargo dependency (ADR 0101): rustc checks it, as
|
|
2
|
+
# builtins/build.sh does, and rust-js reads its metadata (ADR 0102).
|
|
3
|
+
[package]
|
|
4
|
+
name = "rust-js-builtins"
|
|
5
|
+
version = "0.0.1"
|
|
6
|
+
edition = "2024"
|
|
7
|
+
description = "JS's language, as rust-js compiles Rust to it: promises, errors, regular expressions, JSON"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
repository = "https://github.com/rust-js-lang/rust-js"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
# Its Rust, not the repository's tools that build or generate it.
|
|
12
|
+
include = ["src", "README.md"]
|
|
13
|
+
|
|
14
|
+
[lib]
|
|
15
|
+
name = "js"
|
|
16
|
+
path = "src/lib.rs"
|
|
17
|
+
|
|
18
|
+
# rust-js compiles with `--cfg=rust_js` (ADR 0113): a `cfg` Cargo checks for.
|
|
19
|
+
[lints.rust]
|
|
20
|
+
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(rust_js)"] }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Compile the js crate's metadata, which is all programs need (ADR 0102),
|
|
3
|
+
# for the target rust-js checks programs for (ADR 0090):
|
|
4
|
+
#
|
|
5
|
+
# builtins/build.sh -o target/libjs.rmeta
|
|
6
|
+
#
|
|
7
|
+
# Then compile a program with `rust-js app.rs -- --extern js=<that file>`.
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
cd "$(dirname "$0")"
|
|
10
|
+
target=(--target=wasm32-unknown-unknown)
|
|
11
|
+
for arg in "$@"; do
|
|
12
|
+
case "$arg" in --target | --target=*) target=() ;; esac
|
|
13
|
+
done
|
|
14
|
+
# rust-js compiles it, as rustc with rust-js's tool, `rust_js`, known (ADR
|
|
15
|
+
# 0112): $RUST_JS_COMPILER, or this repository's own build.
|
|
16
|
+
compiler=${RUST_JS_COMPILER:-$PWD/../target/debug/rust-js}
|
|
17
|
+
# A packaged one is a JS launcher, run by the JS runtime the build is.
|
|
18
|
+
rust_js=("$compiler")
|
|
19
|
+
case "$compiler" in *.js) rust_js=("${RUST_JS_JS_RUNTIME:-node}" "$compiler") ;; esac
|
|
20
|
+
exec "${rust_js[@]}" --rustc --edition=2024 --crate-type=lib --crate-name=js --emit=metadata ${target[@]+"${target[@]}"} src/lib.rs "$@"
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
//! The JS language for rust-js (ADR 0102): what JS has that Rust's `std`
|
|
2
|
+
//! doesn't, as ReScript's standard library has it. Its promises, errors and
|
|
3
|
+
//! regular expressions, its byte buffers, its JSON, and its global functions,
|
|
4
|
+
//! its timers too, as ReScript's has them: every JS runtime has them. What the
|
|
5
|
+
//! browser adds is the webapi crate's; what `std` has, rust-js maps itself.
|
|
6
|
+
//!
|
|
7
|
+
//! It holds declarations only, so it's never compiled to JS: a program calls
|
|
8
|
+
//! what it declares, and the calls become plain JS.
|
|
9
|
+
|
|
10
|
+
use core::marker::PhantomData;
|
|
11
|
+
|
|
12
|
+
/// `import "./App.css";` in the module's JS, for what a module does when
|
|
13
|
+
/// it's loaded, as a bundler's CSS does (ADRs 0039 and 0110): written where
|
|
14
|
+
/// it's needed, `js::import!("./App.css");`, as stable Rust has no inner
|
|
15
|
+
/// attribute of a tool. It's a `const _` rust-js reads, and writes nothing of.
|
|
16
|
+
#[macro_export]
|
|
17
|
+
macro_rules! import {
|
|
18
|
+
($path:literal) => {
|
|
19
|
+
#[cfg_attr(rust_js, rust_js::import = $path)]
|
|
20
|
+
const _: () = ();
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// The crate's own functions, fields and props are camelCase in JS, as its
|
|
25
|
+
/// variables are (ADRs 0046 and 0110): `js::camel_case!();` at the crate root.
|
|
26
|
+
#[macro_export]
|
|
27
|
+
macro_rules! camel_case {
|
|
28
|
+
() => {
|
|
29
|
+
#[cfg_attr(rust_js, rust_js::camel_case)]
|
|
30
|
+
const _: () = ();
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Any JS object. Every type here and in webapi holds a `PhantomData` of
|
|
35
|
+
/// it, which is how rust-js knows it's a JS object, and so may a program's
|
|
36
|
+
/// own: `pub struct EditorView(PhantomData<JsObject>);` (ADR 0111). A program
|
|
37
|
+
/// never has one by value, only a reference a binding gives: its field is
|
|
38
|
+
/// private, so nothing makes one, and it's neither `Send` nor `Sync`.
|
|
39
|
+
#[cfg_attr(rust_js, rust_js::js_object)]
|
|
40
|
+
pub struct JsObject(PhantomData<*mut ()>);
|
|
41
|
+
|
|
42
|
+
/// A JS [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)
|
|
43
|
+
/// of a `T`. `.await` on one is JS's `await`; a rejected one throws, like a
|
|
44
|
+
/// panic. See ADR 0029.
|
|
45
|
+
pub struct Promise<T>(PhantomData<JsObject>, PhantomData<T>);
|
|
46
|
+
|
|
47
|
+
impl<T> core::future::Future for Promise<T> {
|
|
48
|
+
type Output = T;
|
|
49
|
+
|
|
50
|
+
fn poll(self: core::pin::Pin<&mut Self>, _: &mut core::task::Context<'_>) -> core::task::Poll<T> {
|
|
51
|
+
unreachable!("rust-js compiles `.await` to JS's `await`")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// `promise`, settled either way: its `.await` is `Ok` of what it fulfils
|
|
56
|
+
/// with, or `Err` of what it's rejected with, where the `.await` of the
|
|
57
|
+
/// promise itself would throw. For a promise of the webapi crate's, as
|
|
58
|
+
/// `settle(window::fetch(window, url)).await` is a network error's `Err`
|
|
59
|
+
/// (ADR 0035).
|
|
60
|
+
#[cfg_attr(rust_js, rust_js::link_name = "this")]
|
|
61
|
+
#[allow(unused_variables)]
|
|
62
|
+
pub fn settle<T>(this: Promise<T>) -> Promise<Result<T, &'static JsError>> {
|
|
63
|
+
unreachable!()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
unsafe extern "Rust" {
|
|
67
|
+
/// Run a future without waiting for it, as from an event handler:
|
|
68
|
+
/// `spawn(Box::new(async move { .. }))`. A JS promise is already
|
|
69
|
+
/// running, so in JS this is the promise itself, left unawaited.
|
|
70
|
+
#[link_name = "this"]
|
|
71
|
+
pub safe fn spawn(this: Box<dyn core::future::Future<Output = ()>>);
|
|
72
|
+
|
|
73
|
+
/// [`encodeURIComponent`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent):
|
|
74
|
+
/// `text` for a part of a URL, a query's value say.
|
|
75
|
+
#[link_name = "encodeURIComponent"]
|
|
76
|
+
pub safe fn encode_uri_component(text: &str) -> String;
|
|
77
|
+
|
|
78
|
+
/// [`encodeURI`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/encodeURI):
|
|
79
|
+
/// `text` for a whole URL, its `/`, `?` and `#` kept.
|
|
80
|
+
#[link_name = "encodeURI"]
|
|
81
|
+
pub safe fn encode_uri(text: &str) -> String;
|
|
82
|
+
|
|
83
|
+
/// [`decodeURIComponent`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent):
|
|
84
|
+
/// what `encode_uri_component` made, or the `URIError` of what it can't have.
|
|
85
|
+
#[link_name = "decodeURIComponent"]
|
|
86
|
+
pub safe fn decode_uri_component(text: &str) -> Result<String, &'static JsError>;
|
|
87
|
+
|
|
88
|
+
/// [`decodeURI`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/decodeURI):
|
|
89
|
+
/// what `encode_uri` made, or the `URIError` of what it can't have.
|
|
90
|
+
#[link_name = "decodeURI"]
|
|
91
|
+
pub safe fn decode_uri(text: &str) -> Result<String, &'static JsError>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// What `set_timeout` gives, to clear it with: a number in a browser, an
|
|
95
|
+
/// object in Node.
|
|
96
|
+
pub struct TimeoutId(PhantomData<JsObject>);
|
|
97
|
+
|
|
98
|
+
/// What `set_interval` gives, to clear it with.
|
|
99
|
+
pub struct IntervalId(PhantomData<JsObject>);
|
|
100
|
+
|
|
101
|
+
// Timers: not the language's own, but every JS runtime's, browsers', workers'
|
|
102
|
+
// and Node's, as ReScript's standard library has them (ADR 0102).
|
|
103
|
+
unsafe extern "Rust" {
|
|
104
|
+
/// [`setTimeout(callback, ms)`](https://developer.mozilla.org/docs/Web/API/Window/setTimeout):
|
|
105
|
+
/// `callback` once, after at least `ms` milliseconds.
|
|
106
|
+
#[link_name = "setTimeout"]
|
|
107
|
+
pub safe fn set_timeout(callback: Box<dyn FnOnce()>, ms: u32) -> &'static TimeoutId;
|
|
108
|
+
|
|
109
|
+
/// [`clearTimeout(id)`](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout):
|
|
110
|
+
/// the timeout's `callback` won't run, if it hasn't.
|
|
111
|
+
#[link_name = "clearTimeout"]
|
|
112
|
+
pub safe fn clear_timeout(id: &TimeoutId);
|
|
113
|
+
|
|
114
|
+
/// [`setInterval(callback, ms)`](https://developer.mozilla.org/docs/Web/API/Window/setInterval):
|
|
115
|
+
/// `callback` every `ms` milliseconds, until it's cleared.
|
|
116
|
+
#[link_name = "setInterval"]
|
|
117
|
+
pub safe fn set_interval(callback: Box<dyn FnMut()>, ms: u32) -> &'static IntervalId;
|
|
118
|
+
|
|
119
|
+
/// [`clearInterval(id)`](https://developer.mozilla.org/docs/Web/API/Window/clearInterval):
|
|
120
|
+
/// the interval's `callback` doesn't run again.
|
|
121
|
+
#[link_name = "clearInterval"]
|
|
122
|
+
pub safe fn clear_interval(id: &IntervalId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// A JS [`RegExp`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp),
|
|
126
|
+
/// for what Rust would use the `regex` crate for. `replace` with a string is
|
|
127
|
+
/// here; with a closure, and `matchAll`, are bindings a program declares, typed
|
|
128
|
+
/// for its pattern: a JS replacer is given the match, then each group, then
|
|
129
|
+
/// where it matched, so its Rust type is the pattern's.
|
|
130
|
+
pub struct RegExp(PhantomData<JsObject>);
|
|
131
|
+
|
|
132
|
+
pub mod reg_exp {
|
|
133
|
+
use super::*;
|
|
134
|
+
|
|
135
|
+
unsafe extern "Rust" {
|
|
136
|
+
/// `new RegExp(pattern, flags)`: flags like `"gm"`.
|
|
137
|
+
#[link_name = "new RegExp"]
|
|
138
|
+
pub safe fn new(pattern: &str, flags: &str) -> &'static RegExp;
|
|
139
|
+
|
|
140
|
+
#[link_name = "test"]
|
|
141
|
+
pub safe fn test(this: &RegExp, text: &str) -> bool;
|
|
142
|
+
|
|
143
|
+
/// [`text.replace(pattern, with)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/replace):
|
|
144
|
+
/// the first match, or with the `g` flag each, replaced with `with`,
|
|
145
|
+
/// in which `$1` is the first group and `$&` the match.
|
|
146
|
+
#[link_name = "replace"]
|
|
147
|
+
pub safe fn replace(this: &str, pattern: &RegExp, with: &str) -> String;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// A JS number's methods, for where Rust's own `format!` isn't what's wanted.
|
|
152
|
+
pub mod number {
|
|
153
|
+
unsafe extern "Rust" {
|
|
154
|
+
/// [`x.toFixed(digits)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed):
|
|
155
|
+
/// `x` with `digits` decimals, as JS rounds, a tie away from zero:
|
|
156
|
+
/// `2.5` is `"3"`, where `format!("{:.0}", 2.5)` is `"2"`, to even, as
|
|
157
|
+
/// Rust rounds it. Between ties they agree. `-0` is `"0"`, `1e21` and up
|
|
158
|
+
/// is `String(x)`, `"1e+21"`, and `Infinity` is `"Infinity"`, where
|
|
159
|
+
/// `format!`'s is `"inf"`. `digits` is 0 to 100: more throws a `RangeError`.
|
|
160
|
+
#[link_name = "toFixed"]
|
|
161
|
+
pub safe fn to_fixed(this: f64, digits: u32) -> String;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// JS's [`JSON`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON).
|
|
166
|
+
/// A Rust value's JSON is serde's (ADR 0077): `JSON.stringify` of one, as
|
|
167
|
+
/// rust-js has it in JS, isn't, since `None` is `undefined` and an `i64` a
|
|
168
|
+
/// `BigInt`, which it throws on. So it's typed for what it's exact for.
|
|
169
|
+
pub mod json {
|
|
170
|
+
unsafe extern "Rust" {
|
|
171
|
+
/// [`JSON.stringify(text)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify):
|
|
172
|
+
/// `text`'s JSON, in quotes, with its `"`, `\` and control characters
|
|
173
|
+
/// escaped, which is a JS string literal too.
|
|
174
|
+
#[link_name = "JSON.stringify"]
|
|
175
|
+
pub safe fn stringify(text: &str) -> String;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Whatever a JS function threw, or a promise rejected with: usually an
|
|
180
|
+
/// [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error).
|
|
181
|
+
/// An `extern` function that returns `Result<T, &JsError>` catches it (ADR 0035).
|
|
182
|
+
pub struct JsError(PhantomData<JsObject>);
|
|
183
|
+
|
|
184
|
+
pub mod js_error {
|
|
185
|
+
use super::*;
|
|
186
|
+
|
|
187
|
+
unsafe extern "Rust" {
|
|
188
|
+
/// `String(e)`: an `Error`'s name and message, or any value as text.
|
|
189
|
+
#[link_name = "String"]
|
|
190
|
+
pub safe fn to_string(error: &JsError) -> String;
|
|
191
|
+
|
|
192
|
+
/// `e instanceof Error`: what was thrown is an
|
|
193
|
+
/// [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error),
|
|
194
|
+
/// which has a `message`. A promise may be rejected with anything.
|
|
195
|
+
#[link_name = "instanceof Error"]
|
|
196
|
+
pub safe fn is_error(this: &JsError) -> bool;
|
|
197
|
+
|
|
198
|
+
/// [`e.message`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error/message):
|
|
199
|
+
/// an `Error`'s message, without its name, as `is_error(e)` says it is one.
|
|
200
|
+
#[link_name = "get message"]
|
|
201
|
+
pub safe fn message(this: &JsError) -> String;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// JS's [`Object`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)
|
|
206
|
+
/// functions, for a JS object an API takes or gives.
|
|
207
|
+
pub mod object {
|
|
208
|
+
use super::*;
|
|
209
|
+
|
|
210
|
+
/// [`Object.fromEntries(entries)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries):
|
|
211
|
+
/// a JS object of these keys and values, as an API that takes a
|
|
212
|
+
/// dictionary of them wants: `{ "aria-label": "Rust source" }`.
|
|
213
|
+
#[cfg_attr(rust_js, rust_js::link_name = "Object.fromEntries")]
|
|
214
|
+
#[allow(unused_variables)]
|
|
215
|
+
pub fn from_entries<T>(entries: Vec<(String, T)>) -> &'static JsObject {
|
|
216
|
+
unreachable!()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// [`Object.is(a, b)`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/is):
|
|
220
|
+
/// the same object, or the same value, where `NaN` is itself and `0`
|
|
221
|
+
/// isn't `-0`. Of JS objects, whether they're one: `std::ptr::eq` isn't
|
|
222
|
+
/// rust-js's. Of two Rust types too, since a JS object may be seen as
|
|
223
|
+
/// either: a message's sender, an object, and a frame's `Window`.
|
|
224
|
+
#[cfg_attr(rust_js, rust_js::link_name = "Object.is")]
|
|
225
|
+
#[allow(unused_variables)]
|
|
226
|
+
pub fn is<A: ?Sized, B: ?Sized>(a: &A, b: &B) -> bool {
|
|
227
|
+
unreachable!()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/// A JS [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer):
|
|
232
|
+
/// raw bytes, as `response::array_buffer` gives them.
|
|
233
|
+
pub struct ArrayBuffer(PhantomData<JsObject>);
|
|
234
|
+
|
|
235
|
+
pub mod array_buffer {
|
|
236
|
+
use super::*;
|
|
237
|
+
|
|
238
|
+
unsafe extern "Rust" {
|
|
239
|
+
#[link_name = "get byteLength"]
|
|
240
|
+
pub safe fn byte_length(this: &ArrayBuffer) -> u32;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// A JS [`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array):
|
|
245
|
+
/// a view of the bytes in an `ArrayBuffer`, as `response::bytes` gives them.
|
|
246
|
+
pub struct Uint8Array(PhantomData<JsObject>);
|
|
247
|
+
|
|
248
|
+
pub mod uint8_array {
|
|
249
|
+
use super::*;
|
|
250
|
+
|
|
251
|
+
unsafe extern "Rust" {
|
|
252
|
+
/// A view of all of `buffer`.
|
|
253
|
+
#[link_name = "new Uint8Array"]
|
|
254
|
+
pub safe fn new(buffer: &ArrayBuffer) -> &'static Uint8Array;
|
|
255
|
+
|
|
256
|
+
/// How many bytes it views.
|
|
257
|
+
#[link_name = "get length"]
|
|
258
|
+
pub safe fn length(this: &Uint8Array) -> u32;
|
|
259
|
+
|
|
260
|
+
/// The buffer it views.
|
|
261
|
+
#[link_name = "get buffer"]
|
|
262
|
+
pub safe fn buffer(this: &Uint8Array) -> &'static ArrayBuffer;
|
|
263
|
+
}
|
|
264
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rust-js/resources",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/rust-js-lang/rust-js.git"
|
|
9
|
+
},
|
|
10
|
+
"description": "Pinned build inputs for rust-js React, web, and Serde bindings.",
|
|
11
|
+
"files": [
|
|
12
|
+
"rust-toolchain.toml",
|
|
13
|
+
"react/build.sh",
|
|
14
|
+
"react/cfg.js",
|
|
15
|
+
"react/versions.json",
|
|
16
|
+
"react/src/lib.rs",
|
|
17
|
+
"react/src/event.rs",
|
|
18
|
+
"react/src/dom.rs",
|
|
19
|
+
"react/src/elements.rs",
|
|
20
|
+
"react/Cargo.toml",
|
|
21
|
+
"react/build.rs",
|
|
22
|
+
"webapi/build.sh",
|
|
23
|
+
"webapi/src/lib.rs",
|
|
24
|
+
"webapi/Cargo.toml",
|
|
25
|
+
"builtins/build.sh",
|
|
26
|
+
"builtins/src/lib.rs",
|
|
27
|
+
"builtins/Cargo.toml",
|
|
28
|
+
"serde/Cargo.toml",
|
|
29
|
+
"serde/Cargo.lock",
|
|
30
|
+
"serde/src/lib.rs"
|
|
31
|
+
]
|
|
32
|
+
}
|
package/react/Cargo.toml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# The react crate as a Cargo dependency (ADR 0101): rustc checks it, as
|
|
2
|
+
# react/build.sh does, for the React release `RUST_JS_REACT` names, or the
|
|
3
|
+
# latest the crate knows (ADR 0043); build.rs gives its `cfg`s.
|
|
4
|
+
[package]
|
|
5
|
+
name = "rust-js-react"
|
|
6
|
+
version = "0.0.1"
|
|
7
|
+
edition = "2024"
|
|
8
|
+
description = "React for rust-js: components written in Rust, compiled to the JSX you'd write"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
repository = "https://github.com/rust-js-lang/rust-js"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
# Its Rust, and what build.rs reads, not the repository's tools that build
|
|
13
|
+
# or generate it.
|
|
14
|
+
include = ["src", "build.rs", "versions.json", "README.md"]
|
|
15
|
+
build = "build.rs"
|
|
16
|
+
|
|
17
|
+
[lib]
|
|
18
|
+
name = "react"
|
|
19
|
+
path = "src/lib.rs"
|
|
20
|
+
|
|
21
|
+
[dependencies]
|
|
22
|
+
js = { package = "rust-js-builtins", path = "../builtins", version = "~0.0.1" }
|
|
23
|
+
webapi = { package = "rust-js-webapi", path = "../webapi", version = "~0.0.1" }
|
|
24
|
+
|
|
25
|
+
# rust-js compiles with `--cfg=rust_js` (ADR 0113): a `cfg` Cargo checks for.
|
|
26
|
+
[lints.rust]
|
|
27
|
+
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(rust_js)"] }
|
package/react/build.rs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//! The react crate's `cfg`s for one React release (ADR 0043), as `cfg.js`
|
|
2
|
+
//! gives `react/build.sh` them: `react="18.1"` and so on for every minor
|
|
3
|
+
//! release up to `RUST_JS_REACT`, so an item gated `#[cfg(react = "19.2")]`
|
|
4
|
+
//! exists only from React 19.2. The releases are `versions.json`'s.
|
|
5
|
+
|
|
6
|
+
use std::path::Path;
|
|
7
|
+
|
|
8
|
+
fn main() {
|
|
9
|
+
let versions = Path::new(env!("CARGO_MANIFEST_DIR")).join("versions.json");
|
|
10
|
+
println!("cargo::rerun-if-changed={}", versions.display());
|
|
11
|
+
println!("cargo::rerun-if-env-changed=RUST_JS_REACT");
|
|
12
|
+
let text = std::fs::read_to_string(&versions).expect("react/versions.json");
|
|
13
|
+
let releases = releases(&text);
|
|
14
|
+
let latest = *releases.last().expect("react/versions.json lists no release");
|
|
15
|
+
let wanted = match std::env::var("RUST_JS_REACT") {
|
|
16
|
+
Ok(version) => minor(&version).unwrap_or_else(|| panic!("RUST_JS_REACT: not a React version: {version}")),
|
|
17
|
+
Err(_) => minor(latest).expect("a release is a version"),
|
|
18
|
+
};
|
|
19
|
+
let first = minor(releases[0]).expect("a release is a version");
|
|
20
|
+
assert!(
|
|
21
|
+
first <= wanted,
|
|
22
|
+
"React {}.{} is older than {}, the first release the react crate supports",
|
|
23
|
+
wanted.0,
|
|
24
|
+
wanted.1,
|
|
25
|
+
releases[0]
|
|
26
|
+
);
|
|
27
|
+
if wanted > minor(latest).expect("a release is a version") {
|
|
28
|
+
println!(
|
|
29
|
+
"cargo::warning=React {}.{} is newer than {latest}, the latest release the react crate knows: it has {latest}'s API",
|
|
30
|
+
wanted.0, wanted.1
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
let values: Vec<String> = releases.iter().map(|release| format!("\"{release}\"")).collect();
|
|
34
|
+
println!("cargo::rustc-check-cfg=cfg(react,values({}))", values.join(","));
|
|
35
|
+
for release in releases
|
|
36
|
+
.iter()
|
|
37
|
+
.filter(|release| minor(release).is_some_and(|r| r <= wanted))
|
|
38
|
+
{
|
|
39
|
+
println!("cargo::rustc-cfg=react=\"{release}\"");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// `versions.json`'s releases, `"18.0"` to the latest, as react/generate.ts
|
|
44
|
+
/// writes them: the keys of its `"releases"` object, one to a line.
|
|
45
|
+
fn releases(text: &str) -> Vec<&str> {
|
|
46
|
+
let start = text.find("\"releases\": {").expect("versions.json has releases");
|
|
47
|
+
let end = start + text[start..].find('}').expect("releases end");
|
|
48
|
+
text[start..end]
|
|
49
|
+
.lines()
|
|
50
|
+
.skip(1)
|
|
51
|
+
.filter_map(|line| line.trim().strip_prefix('"')?.split_once('"').map(|(key, _)| key))
|
|
52
|
+
.collect()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// A version's major and minor release: `18.2.0` is `(18, 2)`.
|
|
56
|
+
fn minor(version: &str) -> Option<(u32, u32)> {
|
|
57
|
+
let mut parts = version.split('.');
|
|
58
|
+
Some((parts.next()?.parse().ok()?, parts.next()?.parse().ok()?))
|
|
59
|
+
}
|
package/react/build.sh
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Compile the react crate's metadata (ADRs 0041, 0043), and the webapi and js
|
|
3
|
+
# crates' beside it, which it uses:
|
|
4
|
+
#
|
|
5
|
+
# react/build.sh -o target/libreact.rmeta # the latest React's API
|
|
6
|
+
# react/build.sh -o target/react-18.2/libreact.rmeta --react 18.2.0
|
|
7
|
+
#
|
|
8
|
+
# With `--react`, what later React versions added is left out, so a program
|
|
9
|
+
# using it doesn't compile. Then compile a program with
|
|
10
|
+
# `rust-js app.rs -- --extern react=target/libreact.rmeta -L target`.
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
[ "${1:-}" = "-o" ] && [ -n "${2:-}" ] || {
|
|
13
|
+
echo "usage: react/build.sh -o <dir>/libreact.rmeta [--react <version>] [rustc flags]" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
}
|
|
16
|
+
out=$2
|
|
17
|
+
shift 2
|
|
18
|
+
version=
|
|
19
|
+
if [ "${1:-}" = "--react" ]; then
|
|
20
|
+
version=${2:?--react needs a version, like 18.2.0}
|
|
21
|
+
shift 2
|
|
22
|
+
fi
|
|
23
|
+
mkdir -p "$(dirname "$out")"
|
|
24
|
+
dir=$(cd "$(dirname "$out")" && pwd)
|
|
25
|
+
cd "$(dirname "$0")"
|
|
26
|
+
cfg=()
|
|
27
|
+
cfg_output=$("${RUST_JS_JS_RUNTIME:-node}" cfg.js ${version:+"$version"})
|
|
28
|
+
while IFS= read -r flag; do cfg+=("$flag"); done <<< "$cfg_output"
|
|
29
|
+
../webapi/build.sh -o "$dir/libwebapi.rmeta" "$@"
|
|
30
|
+
# For rust-js's target (ADR 0090), unless another is given.
|
|
31
|
+
target=(--target=wasm32-unknown-unknown)
|
|
32
|
+
for arg in "$@"; do
|
|
33
|
+
case "$arg" in --target | --target=*) target=() ;; esac
|
|
34
|
+
done
|
|
35
|
+
# rust-js compiles it, as rustc with rust-js's tool, `rust_js`, known (ADR
|
|
36
|
+
# 0112): $RUST_JS_COMPILER, or this repository's own build.
|
|
37
|
+
compiler=${RUST_JS_COMPILER:-$PWD/../target/debug/rust-js}
|
|
38
|
+
# A packaged one is a JS launcher, run by the JS runtime the build is.
|
|
39
|
+
rust_js=("$compiler")
|
|
40
|
+
case "$compiler" in *.js) rust_js=("${RUST_JS_JS_RUNTIME:-node}" "$compiler") ;; esac
|
|
41
|
+
# The absolute path lets a program's errors quote this crate's source: a gated
|
|
42
|
+
# item's `#[cfg(react = "..")]`.
|
|
43
|
+
exec "${rust_js[@]}" --rustc --edition=2024 --crate-type=lib --crate-name=react --emit=metadata ${target[@]+"${target[@]}"} "$PWD/src/lib.rs" \
|
|
44
|
+
--extern webapi="$dir/libwebapi.rmeta" --extern js="$dir/libjs.rmeta" "${cfg[@]}" -o "$dir/$(basename "$out")" "$@"
|
package/react/cfg.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The rustc flags that build the react crate for one React version (ADR 0043):
|
|
2
|
+
// `--cfg react="18.1"` and so on for every minor release up to it, so an
|
|
3
|
+
// item gated `#[cfg(react = "19.2")]` exists only from React 19.2.
|
|
4
|
+
//
|
|
5
|
+
// node react/cfg.js 18.2.0 prints one flag per line, for react/build.sh
|
|
6
|
+
//
|
|
7
|
+
// The releases are react/versions.json's, which react/generate.ts reads from
|
|
8
|
+
// React itself.
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
const versions = JSON.parse(readFileSync(new URL("./versions.json", import.meta.url), "utf8"));
|
|
14
|
+
|
|
15
|
+
export const releases = Object.keys(versions.releases);
|
|
16
|
+
const lastRelease = releases.at(-1);
|
|
17
|
+
if (lastRelease === undefined) throw new Error("React versions must contain at least one release");
|
|
18
|
+
export const latest = lastRelease;
|
|
19
|
+
|
|
20
|
+
function minor(version) {
|
|
21
|
+
const [major, minor] = version.split(".").map(Number);
|
|
22
|
+
return [major, minor];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function atMost(a, b) {
|
|
26
|
+
return a[0] < b[0] || (a[0] === b[0] && a[1] <= b[1]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The flags for `version`, and a warning when it's newer than the releases
|
|
31
|
+
* the crate knows, which then gets the latest's API. Throws when it's older
|
|
32
|
+
* than the first release the crate supports.
|
|
33
|
+
*/
|
|
34
|
+
export function cfgFlags(version) {
|
|
35
|
+
const wanted = minor(version);
|
|
36
|
+
if (wanted.some(Number.isNaN)) throw new Error(`not a React version: ${version}`);
|
|
37
|
+
if (!atMost(minor(releases[0]), wanted)) {
|
|
38
|
+
throw new Error(`React ${version} is older than ${releases[0]}, the first release the react crate supports`);
|
|
39
|
+
}
|
|
40
|
+
const values = releases.map((r) => JSON.stringify(r)).join(",");
|
|
41
|
+
const flags = [`--check-cfg=cfg(react,values(${values}))`];
|
|
42
|
+
for (const release of releases) {
|
|
43
|
+
if (atMost(minor(release), wanted)) flags.push(`--cfg=react=${JSON.stringify(release)}`);
|
|
44
|
+
}
|
|
45
|
+
const warning = atMost(wanted, minor(latest))
|
|
46
|
+
? undefined
|
|
47
|
+
: `React ${version} is newer than ${latest}, the latest release the react crate knows: it has ${latest}'s API`;
|
|
48
|
+
return { flags, warning };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
52
|
+
try {
|
|
53
|
+
const { flags, warning } = cfgFlags(process.argv[2] ?? latest);
|
|
54
|
+
if (warning) console.error(`warning: ${warning}`);
|
|
55
|
+
console.log(flags.join("\n"));
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`error: ${error.message}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|