on master help sign in
96 files changed+11463−6233
since splitsnapshots
unifiedsplit
.gitignore+2−1 .travis.yml+54 Cargo.lock+1035 Cargo.toml+81−10 README.md+91−37 benches/benches.rs+118 build.sh+5−22 crates/js-api/Cargo.toml+22 crates/js-api/src/lib.rs+324 example/js/.gitignore−2 example/js/README.md−39 example/js/bootstrap.js−5 example/js/index.html−10 example/js/index.js−3 example/js/package-lock.json−5312 example/js/package.json−35 example/js/webpack.config.js−14 example/rust/.gitignore−6 example/rust/README.md−54 example/rust/src/lib.rs−45 example/rust/tests/web.rs−13 examples/counter/Cargo.toml+33 examples/counter/README.md+21 examples/counter/index.html+13 examples/counter/src/lib.rs+87 examples/game-of-life/Cargo.toml+29 examples/game-of-life/README.md+23 examples/game-of-life/index.html+44 examples/game-of-life/src/lib.rs+218 example/rust/Cargo.toml → examples/hello-world/Cargo.toml+6−7 examples/hello-world/README.md+21 examples/hello-world/index.html+13 examples/hello-world/src/lib.rs+42 examples/input-form/Cargo.toml+32 examples/input-form/README.md+22 examples/input-form/index.html+13 examples/input-form/src/lib.rs+81 examples/js-component/Cargo.toml+26 examples/js-component/README.md+23 examples/js-component/greeting.js+41 examples/js-component/index.html+14 examples/js-component/src/lib.rs+63 examples/moire/Cargo.toml+43 examples/moire/README.md+26 examples/moire/index.html+67 examples/moire/src/colors.rs+162 examples/moire/src/lib.rs+259 examples/sierpinski-triangle/Cargo.toml+28 examples/sierpinski-triangle/README.md+24 examples/sierpinski-triangle/ferris.png examples/sierpinski-triangle/index.html+69 examples/sierpinski-triangle/src/lib.rs+157 examples/todomvc/Cargo.toml+47 examples/todomvc/README.md+33 examples/todomvc/index.html+21 examples/todomvc/src/controller.rs+140 examples/todomvc/src/keys.rs+7 examples/todomvc/src/lib.rs+60 examples/todomvc/src/router.rs+68 examples/todomvc/src/todo.rs+229 examples/todomvc/src/todos.rs+300 examples/todomvc/src/utils.rs+32 examples/todomvc/src/visibility.rs+51 js/change-list-interpreter.js+377 js/change-list.js−148 js/package-lock.json−5 js/package.json−11 js/strace.js+80 publish.sh+11 src/builder.rs+1075 src/cached.rs+229 src/cached_set.rs+186 src/change_list.rs−224 src/change_list/emitter.rs+292 src/change_list/js.rs+40 src/change_list/mod.rs+353 src/change_list/strings.rs+66 src/change_list/traversal.rs+344 src/diff.rs+1125 src/events.rs+138 src/lib.rs+130−9 src/logging.rs+65 src/node.rs+243−66 src/render.rs+168 src/render_context.rs+104 src/strace.rs+22 src/vdom.rs+429−142 tests/web.rs−13 tests/web/cached.js+11 tests/web/cached.rs+289 tests/web/events.rs+250 tests/web/js_api.rs+153 tests/web/keyed.js+25 tests/web/keyed.rs+269 tests/web/main.rs+226 tests/web/render.rs+343
.gitignore+2−1
@@ -1,3 +1,4 @@
11/target
22**/*.rs.bk
3−Cargo.lock
3+wasm-pack.log
4+/.vscode
.travis.ymlnew file+54
@@ -0,0 +1,54 @@
1+language: rust
2+sudo: false
3+
4+INSTALL_NODE_VIA_NVM: &INSTALL_NODE_VIA_NVM
5+ |
6+ rustup target add wasm32-unknown-unknown
7+ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
8+ source ~/.nvm/nvm.sh
9+ nvm install v10.5
10+
11+INSTALL_WASM_PPACK: &INSTALL_WASM_PACK
12+ |
13+ curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
14+
15+install:
16+ - *INSTALL_NODE_VIA_NVM
17+ - *INSTALL_WASM_PACK
18+ - rustup target add wasm32-unknown-unknown
19+
20+matrix:
21+ include:
22+ - name: "build (stable)"
23+ rust: stable
24+ script: INSTALL=1 ./build.sh
25+ - name: "build (beta)"
26+ rust: beta
27+ script: INSTALL=1 ./build.sh
28+ - name: "headless browser tests (firefox)"
29+ rust: stable
30+ addons:
31+ firefox: latest
32+ script: wasm-pack test --firefox --headless -- --features xxx-unstable-internal-use-only
33+ - name: "native tests"
34+ rust: stable
35+ install: echo "no install"
36+ script: cargo test --all
37+
38+ # TODO: A bug in `rustc` is making `js-sys` broken on nightly:
39+ # https://github.com/rust-lang/rust/issues/69315
40+ # - name: "build (nightly)"
41+ # rust: nightly
42+ # script: INSTALL=1 ./build.sh
43+ # - name: "check native benches"
44+ # rust: nightly
45+ # install: echo "no install"
46+ # script: cargo check --benches --features xxx-unstable-internal-use-only
47+
48+ # TODO: `chromedriver` is complaining about ports or something. Temporarily
49+ # disable in CI until we figure it out.
50+ # - name: "headless browser tests (chrome)"
51+ # rust: stable
52+ # addons:
53+ # chrome: stable
54+ # script: wasm-pack test --chrome --headless -- --features xxx-unstable-internal-use-only
Cargo.locknew file+1035
@@ -0,0 +1,1035 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
3+[[package]]
4+name = "anyhow"
5+version = "1.0.25"
6+source = "registry+https://github.com/rust-lang/crates.io-index"
7+
8+[[package]]
9+name = "atty"
10+version = "0.2.13"
11+source = "registry+https://github.com/rust-lang/crates.io-index"
12+dependencies = [
13+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
14+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
15+]
16+
17+[[package]]
18+name = "autocfg"
19+version = "0.1.7"
20+source = "registry+https://github.com/rust-lang/crates.io-index"
21+
22+[[package]]
23+name = "bitflags"
24+version = "1.2.1"
25+source = "registry+https://github.com/rust-lang/crates.io-index"
26+
27+[[package]]
28+name = "bstr"
29+version = "0.2.8"
30+source = "registry+https://github.com/rust-lang/crates.io-index"
31+dependencies = [
32+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
33+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
34+ "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
35+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
36+]
37+
38+[[package]]
39+name = "bumpalo"
40+version = "2.6.0"
41+source = "registry+https://github.com/rust-lang/crates.io-index"
42+
43+[[package]]
44+name = "byteorder"
45+version = "1.3.2"
46+source = "registry+https://github.com/rust-lang/crates.io-index"
47+
48+[[package]]
49+name = "cast"
50+version = "0.2.3"
51+source = "registry+https://github.com/rust-lang/crates.io-index"
52+dependencies = [
53+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
54+]
55+
56+[[package]]
57+name = "cfg-if"
58+version = "0.1.10"
59+source = "registry+https://github.com/rust-lang/crates.io-index"
60+
61+[[package]]
62+name = "clap"
63+version = "2.33.0"
64+source = "registry+https://github.com/rust-lang/crates.io-index"
65+dependencies = [
66+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
67+ "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
68+ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
69+]
70+
71+[[package]]
72+name = "console_error_panic_hook"
73+version = "0.1.6"
74+source = "registry+https://github.com/rust-lang/crates.io-index"
75+dependencies = [
76+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
77+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
78+]
79+
80+[[package]]
81+name = "console_log"
82+version = "0.2.0"
83+source = "registry+https://github.com/rust-lang/crates.io-index"
84+dependencies = [
85+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
86+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
87+]
88+
89+[[package]]
90+name = "criterion"
91+version = "0.3.3"
92+source = "registry+https://github.com/rust-lang/crates.io-index"
93+dependencies = [
94+ "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
95+ "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
96+ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
97+ "criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
98+ "csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
99+ "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
100+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
101+ "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
102+ "oorandom 11.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
103+ "plotters 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
104+ "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
105+ "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
106+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
107+ "serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
108+ "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
109+ "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
110+ "tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
111+ "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
112+]
113+
114+[[package]]
115+name = "criterion-plot"
116+version = "0.4.3"
117+source = "registry+https://github.com/rust-lang/crates.io-index"
118+dependencies = [
119+ "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
120+ "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
121+]
122+
123+[[package]]
124+name = "crossbeam-deque"
125+version = "0.7.2"
126+source = "registry+https://github.com/rust-lang/crates.io-index"
127+dependencies = [
128+ "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
129+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
130+]
131+
132+[[package]]
133+name = "crossbeam-epoch"
134+version = "0.8.0"
135+source = "registry+https://github.com/rust-lang/crates.io-index"
136+dependencies = [
137+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
138+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
139+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
140+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
141+ "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
142+ "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
143+]
144+
145+[[package]]
146+name = "crossbeam-queue"
147+version = "0.2.0"
148+source = "registry+https://github.com/rust-lang/crates.io-index"
149+dependencies = [
150+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
151+]
152+
153+[[package]]
154+name = "crossbeam-utils"
155+version = "0.7.0"
156+source = "registry+https://github.com/rust-lang/crates.io-index"
157+dependencies = [
158+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
159+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
160+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
161+]
162+
163+[[package]]
164+name = "csv"
165+version = "1.1.1"
166+source = "registry+https://github.com/rust-lang/crates.io-index"
167+dependencies = [
168+ "bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
169+ "csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
170+ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
171+ "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
172+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
173+]
174+
175+[[package]]
176+name = "csv-core"
177+version = "0.1.6"
178+source = "registry+https://github.com/rust-lang/crates.io-index"
179+dependencies = [
180+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
181+]
182+
183+[[package]]
184+name = "dodrio"
185+version = "0.2.0"
186+dependencies = [
187+ "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
188+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
189+ "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
190+ "criterion 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
191+ "dodrio-js-api 0.2.0",
192+ "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
193+ "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
194+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
195+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
196+ "longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
197+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
198+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
199+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
200+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
201+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
202+]
203+
204+[[package]]
205+name = "dodrio-counter"
206+version = "0.1.0"
207+dependencies = [
208+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
209+ "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
210+ "dodrio 0.2.0",
211+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
212+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
213+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
214+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
215+]
216+
217+[[package]]
218+name = "dodrio-game-of-life"
219+version = "0.1.0"
220+dependencies = [
221+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
222+ "dodrio 0.2.0",
223+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
224+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
225+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
226+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
227+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
228+]
229+
230+[[package]]
231+name = "dodrio-hello-world"
232+version = "0.1.0"
233+dependencies = [
234+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
235+ "dodrio 0.2.0",
236+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
237+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
238+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
239+]
240+
241+[[package]]
242+name = "dodrio-input-form"
243+version = "0.1.0"
244+dependencies = [
245+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
246+ "dodrio 0.2.0",
247+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
248+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
249+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
250+]
251+
252+[[package]]
253+name = "dodrio-js-api"
254+version = "0.2.0"
255+dependencies = [
256+ "dodrio 0.2.0",
257+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
258+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
259+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
260+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
261+]
262+
263+[[package]]
264+name = "dodrio-js-component"
265+version = "0.1.0"
266+dependencies = [
267+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
268+ "dodrio 0.2.0",
269+ "dodrio-js-api 0.2.0",
270+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
271+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
272+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
273+]
274+
275+[[package]]
276+name = "dodrio-moire"
277+version = "0.1.0"
278+dependencies = [
279+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
280+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
281+ "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
282+ "dodrio 0.2.0",
283+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
284+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
285+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
286+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
287+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
288+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
289+]
290+
291+[[package]]
292+name = "dodrio-sierpinski"
293+version = "0.1.0"
294+dependencies = [
295+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
296+ "dodrio 0.2.0",
297+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
298+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
299+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
300+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
301+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
302+]
303+
304+[[package]]
305+name = "dodrio-todomvc"
306+version = "0.1.0"
307+dependencies = [
308+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
309+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
310+ "console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
311+ "dodrio 0.2.0",
312+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
313+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
314+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
315+ "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
316+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
317+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
318+ "wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
319+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
320+]
321+
322+[[package]]
323+name = "either"
324+version = "1.5.3"
325+source = "registry+https://github.com/rust-lang/crates.io-index"
326+
327+[[package]]
328+name = "futures"
329+version = "0.3.5"
330+source = "registry+https://github.com/rust-lang/crates.io-index"
331+dependencies = [
332+ "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
333+ "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
334+ "futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
335+ "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
336+ "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
337+ "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
338+ "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
339+]
340+
341+[[package]]
342+name = "futures-channel"
343+version = "0.3.5"
344+source = "registry+https://github.com/rust-lang/crates.io-index"
345+dependencies = [
346+ "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
347+ "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
348+]
349+
350+[[package]]
351+name = "futures-core"
352+version = "0.3.5"
353+source = "registry+https://github.com/rust-lang/crates.io-index"
354+
355+[[package]]
356+name = "futures-executor"
357+version = "0.3.5"
358+source = "registry+https://github.com/rust-lang/crates.io-index"
359+dependencies = [
360+ "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
361+ "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
362+ "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
363+]
364+
365+[[package]]
366+name = "futures-io"
367+version = "0.3.5"
368+source = "registry+https://github.com/rust-lang/crates.io-index"
369+
370+[[package]]
371+name = "futures-macro"
372+version = "0.3.5"
373+source = "registry+https://github.com/rust-lang/crates.io-index"
374+dependencies = [
375+ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
376+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
377+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
378+ "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
379+]
380+
381+[[package]]
382+name = "futures-sink"
383+version = "0.3.5"
384+source = "registry+https://github.com/rust-lang/crates.io-index"
385+
386+[[package]]
387+name = "futures-task"
388+version = "0.3.5"
389+source = "registry+https://github.com/rust-lang/crates.io-index"
390+dependencies = [
391+ "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
392+]
393+
394+[[package]]
395+name = "futures-util"
396+version = "0.3.5"
397+source = "registry+https://github.com/rust-lang/crates.io-index"
398+dependencies = [
399+ "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
400+ "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
401+ "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
402+ "futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
403+ "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
404+ "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
405+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
406+ "pin-project 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)",
407+ "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
408+ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
409+ "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
410+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
411+]
412+
413+[[package]]
414+name = "fxhash"
415+version = "0.2.1"
416+source = "registry+https://github.com/rust-lang/crates.io-index"
417+dependencies = [
418+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
419+]
420+
421+[[package]]
422+name = "half"
423+version = "1.6.0"
424+source = "registry+https://github.com/rust-lang/crates.io-index"
425+
426+[[package]]
427+name = "heck"
428+version = "0.3.1"
429+source = "registry+https://github.com/rust-lang/crates.io-index"
430+dependencies = [
431+ "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
432+]
433+
434+[[package]]
435+name = "hermit-abi"
436+version = "0.1.3"
437+source = "registry+https://github.com/rust-lang/crates.io-index"
438+dependencies = [
439+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
440+]
441+
442+[[package]]
443+name = "itertools"
444+version = "0.9.0"
445+source = "registry+https://github.com/rust-lang/crates.io-index"
446+dependencies = [
447+ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
448+]
449+
450+[[package]]
451+name = "itoa"
452+version = "0.4.4"
453+source = "registry+https://github.com/rust-lang/crates.io-index"
454+
455+[[package]]
456+name = "js-sys"
457+version = "0.3.32"
458+source = "registry+https://github.com/rust-lang/crates.io-index"
459+dependencies = [
460+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
461+]
462+
463+[[package]]
464+name = "lazy_static"
465+version = "1.4.0"
466+source = "registry+https://github.com/rust-lang/crates.io-index"
467+
468+[[package]]
469+name = "libc"
470+version = "0.2.66"
471+source = "registry+https://github.com/rust-lang/crates.io-index"
472+
473+[[package]]
474+name = "log"
475+version = "0.4.11"
476+source = "registry+https://github.com/rust-lang/crates.io-index"
477+dependencies = [
478+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
479+]
480+
481+[[package]]
482+name = "longest-increasing-subsequence"
483+version = "0.1.0"
484+source = "registry+https://github.com/rust-lang/crates.io-index"
485+
486+[[package]]
487+name = "memchr"
488+version = "2.2.1"
489+source = "registry+https://github.com/rust-lang/crates.io-index"
490+dependencies = [
491+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
492+]
493+
494+[[package]]
495+name = "memoffset"
496+version = "0.5.3"
497+source = "registry+https://github.com/rust-lang/crates.io-index"
498+dependencies = [
499+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
500+]
501+
502+[[package]]
503+name = "nom"
504+version = "4.2.3"
505+source = "registry+https://github.com/rust-lang/crates.io-index"
506+dependencies = [
507+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
508+ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
509+]
510+
511+[[package]]
512+name = "num-traits"
513+version = "0.2.10"
514+source = "registry+https://github.com/rust-lang/crates.io-index"
515+dependencies = [
516+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
517+]
518+
519+[[package]]
520+name = "num_cpus"
521+version = "1.11.1"
522+source = "registry+https://github.com/rust-lang/crates.io-index"
523+dependencies = [
524+ "hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
525+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
526+]
527+
528+[[package]]
529+name = "once_cell"
530+version = "1.3.1"
531+source = "registry+https://github.com/rust-lang/crates.io-index"
532+
533+[[package]]
534+name = "oorandom"
535+version = "11.1.0"
536+source = "registry+https://github.com/rust-lang/crates.io-index"
537+
538+[[package]]
539+name = "pin-project"
540+version = "0.4.16"
541+source = "registry+https://github.com/rust-lang/crates.io-index"
542+dependencies = [
543+ "pin-project-internal 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)",
544+]
545+
546+[[package]]
547+name = "pin-project-internal"
548+version = "0.4.16"
549+source = "registry+https://github.com/rust-lang/crates.io-index"
550+dependencies = [
551+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
552+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
553+ "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
554+]
555+
556+[[package]]
557+name = "pin-utils"
558+version = "0.1.0"
559+source = "registry+https://github.com/rust-lang/crates.io-index"
560+
561+[[package]]
562+name = "plotters"
563+version = "0.2.12"
564+source = "registry+https://github.com/rust-lang/crates.io-index"
565+dependencies = [
566+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
567+ "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
568+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
569+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
570+]
571+
572+[[package]]
573+name = "proc-macro-hack"
574+version = "0.5.11"
575+source = "registry+https://github.com/rust-lang/crates.io-index"
576+dependencies = [
577+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
578+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
579+ "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
580+]
581+
582+[[package]]
583+name = "proc-macro-nested"
584+version = "0.1.3"
585+source = "registry+https://github.com/rust-lang/crates.io-index"
586+
587+[[package]]
588+name = "proc-macro2"
589+version = "1.0.18"
590+source = "registry+https://github.com/rust-lang/crates.io-index"
591+dependencies = [
592+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
593+]
594+
595+[[package]]
596+name = "quote"
597+version = "1.0.2"
598+source = "registry+https://github.com/rust-lang/crates.io-index"
599+dependencies = [
600+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
601+]
602+
603+[[package]]
604+name = "rayon"
605+version = "1.3.0"
606+source = "registry+https://github.com/rust-lang/crates.io-index"
607+dependencies = [
608+ "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
609+ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
610+ "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
611+]
612+
613+[[package]]
614+name = "rayon-core"
615+version = "1.7.0"
616+source = "registry+https://github.com/rust-lang/crates.io-index"
617+dependencies = [
618+ "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
619+ "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
620+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
621+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
622+ "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
623+]
624+
625+[[package]]
626+name = "regex"
627+version = "1.3.3"
628+source = "registry+https://github.com/rust-lang/crates.io-index"
629+dependencies = [
630+ "regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
631+]
632+
633+[[package]]
634+name = "regex-automata"
635+version = "0.1.8"
636+source = "registry+https://github.com/rust-lang/crates.io-index"
637+dependencies = [
638+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
639+]
640+
641+[[package]]
642+name = "regex-syntax"
643+version = "0.6.13"
644+source = "registry+https://github.com/rust-lang/crates.io-index"
645+
646+[[package]]
647+name = "rustc_version"
648+version = "0.2.3"
649+source = "registry+https://github.com/rust-lang/crates.io-index"
650+dependencies = [
651+ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
652+]
653+
654+[[package]]
655+name = "ryu"
656+version = "1.0.2"
657+source = "registry+https://github.com/rust-lang/crates.io-index"
658+
659+[[package]]
660+name = "same-file"
661+version = "1.0.5"
662+source = "registry+https://github.com/rust-lang/crates.io-index"
663+dependencies = [
664+ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
665+]
666+
667+[[package]]
668+name = "scoped-tls"
669+version = "1.0.0"
670+source = "registry+https://github.com/rust-lang/crates.io-index"
671+
672+[[package]]
673+name = "scopeguard"
674+version = "1.0.0"
675+source = "registry+https://github.com/rust-lang/crates.io-index"
676+
677+[[package]]
678+name = "semver"
679+version = "0.9.0"
680+source = "registry+https://github.com/rust-lang/crates.io-index"
681+dependencies = [
682+ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
683+]
684+
685+[[package]]
686+name = "semver-parser"
687+version = "0.7.0"
688+source = "registry+https://github.com/rust-lang/crates.io-index"
689+
690+[[package]]
691+name = "serde"
692+version = "1.0.114"
693+source = "registry+https://github.com/rust-lang/crates.io-index"
694+dependencies = [
695+ "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
696+]
697+
698+[[package]]
699+name = "serde_cbor"
700+version = "0.11.1"
701+source = "registry+https://github.com/rust-lang/crates.io-index"
702+dependencies = [
703+ "half 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
704+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
705+]
706+
707+[[package]]
708+name = "serde_derive"
709+version = "1.0.114"
710+source = "registry+https://github.com/rust-lang/crates.io-index"
711+dependencies = [
712+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
713+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
714+ "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
715+]
716+
717+[[package]]
718+name = "serde_json"
719+version = "1.0.57"
720+source = "registry+https://github.com/rust-lang/crates.io-index"
721+dependencies = [
722+ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
723+ "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
724+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
725+]
726+
727+[[package]]
728+name = "slab"
729+version = "0.4.2"
730+source = "registry+https://github.com/rust-lang/crates.io-index"
731+
732+[[package]]
733+name = "sourcefile"
734+version = "0.1.4"
735+source = "registry+https://github.com/rust-lang/crates.io-index"
736+
737+[[package]]
738+name = "syn"
739+version = "1.0.33"
740+source = "registry+https://github.com/rust-lang/crates.io-index"
741+dependencies = [
742+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
743+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
744+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
745+]
746+
747+[[package]]
748+name = "textwrap"
749+version = "0.11.0"
750+source = "registry+https://github.com/rust-lang/crates.io-index"
751+dependencies = [
752+ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
753+]
754+
755+[[package]]
756+name = "tinytemplate"
757+version = "1.1.0"
758+source = "registry+https://github.com/rust-lang/crates.io-index"
759+dependencies = [
760+ "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
761+ "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
762+]
763+
764+[[package]]
765+name = "unicode-segmentation"
766+version = "1.6.0"
767+source = "registry+https://github.com/rust-lang/crates.io-index"
768+
769+[[package]]
770+name = "unicode-width"
771+version = "0.1.6"
772+source = "registry+https://github.com/rust-lang/crates.io-index"
773+
774+[[package]]
775+name = "unicode-xid"
776+version = "0.2.0"
777+source = "registry+https://github.com/rust-lang/crates.io-index"
778+
779+[[package]]
780+name = "version_check"
781+version = "0.1.5"
782+source = "registry+https://github.com/rust-lang/crates.io-index"
783+
784+[[package]]
785+name = "walkdir"
786+version = "2.3.1"
787+source = "registry+https://github.com/rust-lang/crates.io-index"
788+dependencies = [
789+ "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
790+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
791+ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
792+]
793+
794+[[package]]
795+name = "wasm-bindgen"
796+version = "0.2.55"
797+source = "registry+https://github.com/rust-lang/crates.io-index"
798+dependencies = [
799+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
800+ "wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
801+]
802+
803+[[package]]
804+name = "wasm-bindgen-backend"
805+version = "0.2.55"
806+source = "registry+https://github.com/rust-lang/crates.io-index"
807+dependencies = [
808+ "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
809+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
810+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
811+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
812+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
813+ "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
814+ "wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
815+]
816+
817+[[package]]
818+name = "wasm-bindgen-futures"
819+version = "0.4.5"
820+source = "registry+https://github.com/rust-lang/crates.io-index"
821+dependencies = [
822+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
823+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
824+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
825+ "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
826+]
827+
828+[[package]]
829+name = "wasm-bindgen-macro"
830+version = "0.2.55"
831+source = "registry+https://github.com/rust-lang/crates.io-index"
832+dependencies = [
833+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
834+ "wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
835+]
836+
837+[[package]]
838+name = "wasm-bindgen-macro-support"
839+version = "0.2.55"
840+source = "registry+https://github.com/rust-lang/crates.io-index"
841+dependencies = [
842+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
843+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
844+ "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
845+ "wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
846+ "wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
847+]
848+
849+[[package]]
850+name = "wasm-bindgen-shared"
851+version = "0.2.55"
852+source = "registry+https://github.com/rust-lang/crates.io-index"
853+
854+[[package]]
855+name = "wasm-bindgen-test"
856+version = "0.3.5"
857+source = "registry+https://github.com/rust-lang/crates.io-index"
858+dependencies = [
859+ "console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
860+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
861+ "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
862+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
863+ "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
864+ "wasm-bindgen-test-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
865+]
866+
867+[[package]]
868+name = "wasm-bindgen-test-macro"
869+version = "0.3.5"
870+source = "registry+https://github.com/rust-lang/crates.io-index"
871+dependencies = [
872+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
873+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
874+]
875+
876+[[package]]
877+name = "wasm-bindgen-webidl"
878+version = "0.2.55"
879+source = "registry+https://github.com/rust-lang/crates.io-index"
880+dependencies = [
881+ "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
882+ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
883+ "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
884+ "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
885+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
886+ "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
887+ "wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
888+ "weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
889+]
890+
891+[[package]]
892+name = "web-sys"
893+version = "0.3.32"
894+source = "registry+https://github.com/rust-lang/crates.io-index"
895+dependencies = [
896+ "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
897+ "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
898+ "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
899+ "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
900+ "wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
901+]
902+
903+[[package]]
904+name = "weedle"
905+version = "0.10.0"
906+source = "registry+https://github.com/rust-lang/crates.io-index"
907+dependencies = [
908+ "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
909+]
910+
911+[[package]]
912+name = "winapi"
913+version = "0.3.8"
914+source = "registry+https://github.com/rust-lang/crates.io-index"
915+dependencies = [
916+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
917+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
918+]
919+
920+[[package]]
921+name = "winapi-i686-pc-windows-gnu"
922+version = "0.4.0"
923+source = "registry+https://github.com/rust-lang/crates.io-index"
924+
925+[[package]]
926+name = "winapi-util"
927+version = "0.1.2"
928+source = "registry+https://github.com/rust-lang/crates.io-index"
929+dependencies = [
930+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
931+]
932+
933+[[package]]
934+name = "winapi-x86_64-pc-windows-gnu"
935+version = "0.4.0"
936+source = "registry+https://github.com/rust-lang/crates.io-index"
937+
938+[metadata]
939+"checksum anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14"
940+"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
941+"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
942+"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
943+"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
944+"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708"
945+"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
946+"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
947+"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
948+"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
949+"checksum console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
950+"checksum console_log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494"
951+"checksum criterion 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8"
952+"checksum criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
953+"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
954+"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
955+"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700"
956+"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
957+"checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"
958+"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
959+"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
960+"checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
961+"checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
962+"checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
963+"checksum futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
964+"checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
965+"checksum futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
966+"checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc"
967+"checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626"
968+"checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
969+"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
970+"checksum half 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177"
971+"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
972+"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
973+"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
974+"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
975+"checksum js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "1c840fdb2167497b0bd0db43d6dfe61e91637fa72f9d061f8bd17ddc44ba6414"
976+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
977+"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
978+"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
979+"checksum longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86"
980+"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
981+"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
982+"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
983+"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
984+"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
985+"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
986+"checksum oorandom 11.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405"
987+"checksum pin-project 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)" = "81d480cb4e89522ccda96d0eed9af94180b7a5f93fb28f66e1fd7d68431663d1"
988+"checksum pin-project-internal 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)" = "a82996f11efccb19b685b14b5df818de31c1edcee3daa256ab5775dd98e72feb"
989+"checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
990+"checksum plotters 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "4e3bb8da247d27ae212529352020f3e5ee16e83c0c258061d27b08ab92675eeb"
991+"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
992+"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
993+"checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
994+"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
995+"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
996+"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
997+"checksum regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87"
998+"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9"
999+"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
1000+"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
1001+"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
1002+"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
1003+"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
1004+"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
1005+"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
1006+"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1007+"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
1008+"checksum serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
1009+"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
1010+"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
1011+"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1012+"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
1013+"checksum syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
1014+"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
1015+"checksum tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f"
1016+"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
1017+"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
1018+"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
1019+"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
1020+"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
1021+"checksum wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "29ae32af33bacd663a9a28241abecf01f2be64e6a185c6139b04f18b6385c5f2"
1022+"checksum wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "1845584bd3593442dc0de6e6d9f84454a59a057722f36f005e44665d6ab19d85"
1023+"checksum wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1458706aa1b8fe6898d19433c9f110d93a05d1f22ae6adf55810409a94df34b4"
1024+"checksum wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "87fcc747e6b73c93d22c947a6334644d22cfec5abd8b66238484dc2b0aeb9fe4"
1025+"checksum wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3dc4b3f2c4078c8c4a5f363b92fcf62604c5913cbd16c6ff5aaf0f74ec03f570"
1026+"checksum wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ca0b78d6d3be8589b95d1d49cdc0794728ca734adf36d7c9f07e6459508bb53d"
1027+"checksum wasm-bindgen-test 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ed5833682054e941db0b7e2b4877b61bbaafd680608e40d268e070d34558b305"
1028+"checksum wasm-bindgen-test-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9c5dcf70d21d0ff0ea5cdb97dba9a9b56ddad4a9bd10c8912eedc55618be022f"
1029+"checksum wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3126356474ceb717c8fb5549ae387c9fbf4872818454f4d87708bee997214bb5"
1030+"checksum web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "98405c0a2e722ed3db341b4c5b70eb9fe0021621f7350bab76df93b09b649bbf"
1031+"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164"
1032+"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
1033+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1034+"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
1035+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
Cargo.toml+81−10
@@ -1,34 +1,105 @@
11[package]
2−name = "dodrio"
3−version = "0.1.0"
42authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
3+categories = ["wasm", "web-programming"]
4+description = "A fast, bump-allocated virtual DOM library."
55edition = "2018"
6+keywords = ["virtual-dom", "vdom", "render", "bump"]
7+license = "MPL-2.0"
8+name = "dodrio"
9+readme = "./README.md"
10+repository = "https://github.com/fitzgen/dodrio"
11+version = "0.2.0"
12+
13+[badges]
14+travis-ci = { repository = "fitzgen/dodrio" }
15+
16+[lib]
17+path = "src/lib.rs"
18+bench = false
19+
20+[[test]]
21+name = "web"
22+path = "tests/web/main.rs"
23+required-features = ["xxx-unstable-internal-use-only"]
24+
25+[[bench]]
26+name = "benches"
27+path = "benches/benches.rs"
28+harness = false
29+required-features = ["xxx-unstable-internal-use-only"]
630
731[features]
8−default = []
32+# Don't rely on this feature! Its behavior will change and it could go away at
33+# any time. This enables monkey-patching DOM methods so that we wrap them and
34+# trace what DOM methods are used and how many times they are invoked when
35+# applying our emitted change lists to the physical DOM. This is useful for
36+# evaluating the differences between how Dodrio applies changes from virtual
37+# nodes to the physical DOM versus how other virtual DOM libraries do the same.
38+xxx-unstable-strace = []
39+
40+# Don't use this feature! It only exists for exposing otherwise-internal APIs
41+# for testing and benchmarks.
42+xxx-unstable-internal-use-only = []
943
1044[dependencies]
11−bumpalo = "1.0.2"
12−wasm-bindgen = "0.2.25"
13−js-sys = "0.3.2"
45+bumpalo = "2.6.0"
46+cfg-if = "0.1.10"
47+fxhash = "0.2.1"
48+js-sys = "0.3.32"
49+log = { version = "0.4.11", optional = true }
50+longest-increasing-subsequence = "0.1.0"
51+serde = { version = "1.0.114", optional = true }
52+wasm-bindgen = "0.2.55"
53+wasm-bindgen-futures = "0.4.5"
1454
1555[dependencies.web-sys]
16−version = "0.3.2"
56+version = "0.3.32"
1757features = [
58+ "console",
1859 "Document",
1960 "Element",
61+ "Event",
2062 "Node",
2163 "Window",
2264]
2365
2466[dev-dependencies]
25−wasm-bindgen-test = "0.2"
67+console_log = "0.2.0"
68+dodrio-js-api = { version = "=0.2.0", path = "./crates/js-api" }
69+futures = "0.3.5"
70+log = "0.4.11"
71+wasm-bindgen-test = "0.3.5"
72+
73+[dev-dependencies.web-sys]
74+version = "0.3.32"
75+features = [
76+ "Attr",
77+ "EventTarget",
78+ "HtmlElement",
79+ "NamedNodeMap",
80+ "NodeList",
81+]
82+
83+[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
84+criterion = "0.3.3"
85+
86+[profile.bench]
87+debug = true
2688
2789[profile.release]
28−# Tell `rustc` to optimize for small code size.
90+incremental = false
91+lto = true
2992opt-level = "s"
3093
3194[workspace]
3295members = [
33− "./example/rust"
96+ "./crates/js-api",
97+ "./examples/counter",
98+ "./examples/game-of-life",
99+ "./examples/hello-world",
100+ "./examples/input-form",
101+ "./examples/js-component",
102+ "./examples/moire",
103+ "./examples/sierpinski-triangle",
104+ "./examples/todomvc",
34105]
README.md+91−37
@@ -1,54 +1,108 @@
1−<meta charset="utf-8"/>
1+# Dodrio
22
3−# 🦀🕸️ `wasm-pack-template`
3+A fast, bump-allocated virtual DOM library for Rust and WebAssembly. Note that
4+Dodrio is still **experimental**.
45
5−A template for kick starting a Rust and WebAssembly project using
6−[`wasm-pack`](https://github.com/rustwasm/wasm-pack).
6+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
7+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
78
8−This template is designed for compiling Rust libraries into WebAssembly and
9−publishing the resulting package to NPM.
109
11−* Want to use the published NPM package in a Website? [Check out
12− `create-wasm-app`.](https://github.com/rustwasm/create-wasm-app)
13−* Want to make a monorepo-style Website without publishing to NPM? Check out
14− [`rust-webpack-template`](https://github.com/rustwasm/rust-webpack-template)
15− and/or
16− [`rust-parcel-template`](https://github.com/rustwasm/rust-parcel-template).
10+- [Warning](#warning)
11+- [Examples](#examples)
12+- [Design](#design)
13+ - [Bump Allocation](#bump-allocation)
14+ - [Change List as Stack Machine](#change-list-as-stack-machine)
15+ - [Library — Not Framework](#library--not-framework)
1716
18−## 🔋 Batteries Included
17+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
1918
20−* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating
21− between WebAssembly and JavaScript.
22−* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook)
23− for logging panic messages to the developer console.
24−* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized
25− for small code size.
19+## Warning
2620
27−## 🚴 Usage
21+I reiterate that Dodrio is in a very **experimental** state. It probably has
22+bugs, and no one is using it in production.
2823
29−### 🐑 Use `cargo generate` to Clone this Template
24+## Examples
3025
31−[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate)
26+Here is the classic "Hello, World!" example:
3227
33−```
34−cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
35−cd my-project
28+```rust
29+struct Hello {
30+ who: String,
31+}
32+
33+impl Render for Hello {
34+ fn render<'a>(&self, cx: &mut RenderContext<a>) -> Node<'a> {
35+ let who = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
36+ div(cx)
37+ .children([text(who.into_bump_str())])
38+ .finish()
39+ }
40+}
3641```
3742
38−### 🛠️ Build with `wasm-pack build`
43+More examples can be found in [the `examples`
44+directory](https://github.com/fitzgen/dodrio/tree/master/examples), including:
3945
40−```
41−wasm-pack build
42−```
46+* [`counter`](https://github.com/fitzgen/dodrio/tree/master/examples/counter):
47+ Incrementing and decrementing a counter.
48+* [`input-form`](https://github.com/fitzgen/dodrio/tree/master/examples/input-form):
49+ Reading an `<input>` and displaying its contents.
50+* [`todomvc`](https://github.com/fitzgen/dodrio/tree/master/examples/todomvc):
51+ An implementation of the infamous TodoMVC application.
52+* [`moire`](https://github.com/fitzgen/dodrio/tree/master/examples/moire): The
53+ WebRender Moiré patterns demo.
54+* [`game-of-life`](https://github.com/fitzgen/dodrio/tree/master/examples/game-of-life):
55+ The Rust and WebAssembly book's Game of Life tutorial rendered with Dodrio
56+ instead of to 2D canvas.
57+* [`js-component`](https://github.com/fitzgen/dodrio/tree/master/examples/js-component):
58+ Defines a rendering component in JavaScript with the `dodrio-js-api` crate.
4359
44−### 🔬 Test in Headless Browsers with `wasm-pack test`
60+## Cargo Features
4561
46−```
47−wasm-pack test --headless --firefox
48−```
62+* `log` &mdash; enable debugging-oriented log messages with the `log` crate's
63+ facade. You still have to initialize a logger for the messages to go anywhere,
64+ such as [`console_log`](https://github.com/iamcodemaker/console_log).
4965
50−### 🎁 Publish to NPM with `wasm-pack publish`
66+* `serde` &mdash; enable `serde::{Serialize, Deserialize}` implementations for
67+ `Cached<R>` where `R` is serializable and deserializable.
5168
52−```
53−wasm-pack publish
54−```
69+## Design
70+
71+### Bump Allocation
72+
73+Bump allocation is essentially the fastest method of allocating objects. It has
74+constraints, but works particularly well when allocation lifetimes match program
75+phases. And virtual DOMs are very phase oriented.
76+
77+Dodrio maintains three bump allocation arenas:
78+
79+1. The newest, most up-to-date virtual DOM. The virtual DOM nodes themselves and
80+ any temporary containers needed while creating them are allocated into this
81+ arena.
82+2. The previous virtual DOM. This reflects the current state of the physical
83+ DOM.
84+3. The difference between (1) and (2). This is a sequence of DOM mutation
85+ operations — colloquially known as a "change list" — which if applied to
86+ the physical DOM, will make the physical DOM match (1).
87+
88+Rendering happens as follows:
89+
90+1. The application state is rendered into bump allocation arena (1).
91+2. (1) is diffed with (2) and the changes are emitted into (3).
92+3. JavaScript code applies the change list in (3) to the physical DOM.
93+4. (1) and (2) are swapped, double-buffering style, and the new (1) has its bump
94+ allocation pointer reset, as does (3).
95+5. Rinse and repeat.
96+
97+### Change List as Stack Machine
98+
99+The change list that represents the difference between how the physical DOM
100+currently looks, and our ideal virtual DOM state is encoded in a tiny stack
101+machine language. A stack machine works particularly well for applying DOM
102+diffs, a task that is essentially a tree traversal.
103+
104+### Library — Not Framework
105+
106+Dodrio is just a library. (And did I mention it is experimental?!) It is not a
107+full-fledged, complete, batteries-included solution for all frontend Web
108+development. And it never intends to become that either.
benches/benches.rsnew file+118
@@ -0,0 +1,118 @@
1+#![cfg(all(
2+ feature = "xxx-unstable-internal-use-only",
3+ not(target_arch = "wasm32")
4+))]
5+
6+use criterion::{
7+ black_box, criterion_group, criterion_main, Benchmark, Criterion, ParameterizedBenchmark,
8+ Throughput,
9+};
10+use dodrio::{
11+ builder::*,
12+ bumpalo::{self, Bump},
13+ CachedSet, Node, Render, RenderContext, Vdom,
14+};
15+use std::cell::RefCell;
16+use std::convert::TryInto;
17+
18+/// The simplest thing we can render: `<div/>`.
19+struct Empty;
20+impl<'a> Render<'a> for Empty {
21+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
22+ div(&cx).finish()
23+ }
24+}
25+
26+/// Render a list that is `self.0` items long, has attributes and listeners.
27+struct SimpleList(usize);
28+impl<'a> Render<'a> for SimpleList {
29+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
30+ let mut children = bumpalo::collections::Vec::with_capacity_in(self.0, cx.bump);
31+ children.extend((0..self.0).map(|_| {
32+ li(&cx)
33+ .attr("class", "my-list-item")
34+ .on("click", |_root, _vdom, _event| {
35+ panic!("no one should call this")
36+ })
37+ .children([text("a list item")])
38+ .finish()
39+ }));
40+ ol(&cx).attr("id", "my-list").children(children).finish()
41+ }
42+}
43+
44+fn criterion_benchmark(c: &mut Criterion) {
45+ c.bench(
46+ "render",
47+ Benchmark::new("empty", {
48+ let mut bump = Bump::new();
49+ let cached_set = RefCell::new(CachedSet::default());
50+ let mut templates = Default::default();
51+ move |b| {
52+ bump.reset();
53+ let mut cx = RenderContext::new(&bump, &cached_set, &mut templates);
54+ b.iter(|| {
55+ black_box(Empty.render(&mut cx));
56+ })
57+ }
58+ }),
59+ );
60+
61+ c.bench(
62+ "render",
63+ ParameterizedBenchmark::new(
64+ "list",
65+ {
66+ let mut bump = Bump::new();
67+ let cached_set = RefCell::new(CachedSet::default());
68+ let mut templates = Default::default();
69+ move |b, n| {
70+ bump.reset();
71+ let mut cx = RenderContext::new(&bump, &cached_set, &mut templates);
72+ b.iter(|| {
73+ black_box(SimpleList(*n).render(&mut cx));
74+ })
75+ }
76+ },
77+ vec![100, 1_000, 10_000],
78+ )
79+ .throughput(|n| Throughput::Elements((*n).try_into().unwrap())),
80+ );
81+
82+ c.bench(
83+ "render-and-diff",
84+ ParameterizedBenchmark::new(
85+ "same-list",
86+ |b, &n| {
87+ let vdom = Vdom::new(&(), SimpleList(n));
88+ b.iter(|| {
89+ vdom.immediately_render_and_diff(SimpleList(n));
90+ black_box(&vdom);
91+ })
92+ },
93+ vec![100, 1_000, 10_000],
94+ )
95+ .with_function("empty-to-full-list-to-empty", |b, &n| {
96+ b.iter(|| {
97+ let vdom = Vdom::new(&(), Empty);
98+ vdom.immediately_render_and_diff(SimpleList(n));
99+ black_box(&vdom);
100+ vdom.immediately_render_and_diff(Empty);
101+ black_box(&vdom);
102+ });
103+ })
104+ .with_function("append-one-and-remove-one", |b, &n| {
105+ let vdom = Vdom::new(&(), SimpleList(n));
106+ b.iter(|| {
107+ vdom.immediately_render_and_diff(SimpleList(n + 1));
108+ black_box(&vdom);
109+ vdom.immediately_render_and_diff(SimpleList(n));
110+ black_box(&vdom);
111+ })
112+ })
113+ .throughput(|n| Throughput::Elements((*n).try_into().unwrap())),
114+ );
115+}
116+
117+criterion_group!(benches, criterion_benchmark);
118+criterion_main!(benches);
build.sh+5−22
@@ -3,26 +3,9 @@
33cd $(dirname $0)
44set -eux
55
6−INSTALL=${INSTALL:-""}
6+cargo fmt --all || true
7+cargo build --all --target wasm32-unknown-unknown
78
8−function with_dir {
9− pushd $1
10− shift
11− $@
12− popd
13−}
14−
15−cargo fmt --all
16−cargo check
17−wasm-pack build ./example/rust
18−
19−if test "$INSTALL" != ""; then
20− with_dir ./js npm link
21− with_dir ./example/rust/pkg npm link
22− with_dir ./example/rust/pkg npm link dodrio
23− with_dir ./example/js npm install
24− with_dir ./example/js npm link dodrio
25− with_dir ./example/js npm link dodrio-example
26−fi
27−
28−with_dir ./example/js/ npm run build
9+for x in ./examples/*; do
10+ wasm-pack build --target web "$x"
11+done
crates/js-api/Cargo.tomlnew file+22
@@ -0,0 +1,22 @@
1+[package]
2+authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
3+categories = ["wasm", "web-programming"]
4+description = "An API for implementing Dodrio rendering components with JavaScript"
5+edition = "2018"
6+keywords = ["javascript", "js", "vdom", "virtual-dom"]
7+license = "MPL-2.0"
8+name = "dodrio-js-api"
9+repository = "https://github.com/fitzgen/dodrio"
10+version = "0.2.0"
11+
12+[badges]
13+travis-ci = { repository = "fitzgen/dodrio" }
14+
15+[dependencies]
16+dodrio = { version = "=0.2.0", path = "../.." }
17+wasm-bindgen = "0.2.55"
18+js-sys = "0.3.32"
19+wasm-bindgen-futures = "0.4.5"
20+
21+[dev-dependencies]
22+wasm-bindgen-test = "0.3.5"
crates/js-api/src/lib.rsnew file+324
@@ -0,0 +1,324 @@
1+/*!
2+
3+Implementing `dodrio` render components with JavaScript.
4+
5+This crate provides a Rust type `JsRender` that wraps a JavaScript object with a
6+`render` method. `JsRender` implements `dodrio::Render` by calling its wrapped
7+object's `render` method to get a JavaScript virtual DOM represented as a tree
8+of JavaScript values. It then converts this tree of JavaScript values into
9+`dodrio`'s normal bump-allocated virtual DOM representation.
10+
11+This is likely much slower than rendering virtual DOMs directly into the bump
12+allocator from the Rust side of things! Additionally, the shape of the
13+JavaScript virtual DOM is a bit funky and unidiomatic. Keep in mind that this
14+crate exists as a proof of concept for integrating JavaScript components into
15+`dodrio` -- which is itself *also* experimental -- and so this crate definitely
16+has some rough edges.
17+
18+# Example
19+
20+Here is a JavaScript implementation of a rendering component:
21+
22+```javascript
23+class Greeting {
24+ constructor(who) {
25+ this.who = who;
26+ }
27+
28+ render() {
29+ return {
30+ tagName: "p",
31+ attributes: [
32+ {
33+ name: "class",
34+ value: "greeting",
35+ },
36+ ],
37+ listeners: [
38+ {
39+ on: "click",
40+ callback: this.onClick.bind(this),
41+ }
42+ ],
43+ children: [
44+ "Hello, ",
45+ {
46+ tagName: "strong",
47+ children: [this.who],
48+ }
49+ ],
50+ };
51+ }
52+
53+ async onClick(vdom, event) {
54+ // Be more excited!
55+ this.who += "!";
56+
57+ // Schedule a re-render.
58+ await vdom.render();
59+
60+ console.log("re-rendering finished!");
61+ }
62+}
63+```
64+
65+And here is a Rust rendering component that internally uses the JS rendering
66+component:
67+
68+```rust,no_run
69+use dodrio::{Node, Render, RenderContext, Vdom};
70+use dodrio_js_api::JsRender;
71+use js_sys::Object;
72+use wasm_bindgen::prelude::*;
73+
74+#[wasm_bindgen]
75+extern {
76+ // Import the JS `Greeting` class.
77+ #[wasm_bindgen(extends = Object)]
78+ #[derive(Clone, Debug)]
79+ type Greeting;
80+
81+ // And the `Greeting` class's constructor.
82+ #[wasm_bindgen(constructor)]
83+ fn new(who: &str) -> Greeting;
84+}
85+
86+/// This is our Rust rendering component that wraps the JS rendering component.
87+pub struct GreetingViaJs {
88+ js: JsRender,
89+}
90+
91+impl GreetingViaJs {
92+ /// Create a new `GreetingViaJs`, which will internally create a new JS
93+ /// `Greeting`.
94+ pub fn new(who: &str) -> GreetingViaJs {
95+ let js = JsRender::new(Greeting::new(who));
96+ GreetingViaJs { js }
97+ }
98+}
99+
100+/// And finally the `Render` implementation! This adds a `<p>` element and some
101+/// text around whatever the inner JS `Greeting` component renders.
102+impl<'a> Render<'a> for GreetingViaJs {
103+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
104+ use dodrio::builder::*;
105+ p(&cx)
106+ .children([
107+ text("JavaScript says: "),
108+ self.js.render(cx),
109+ ])
110+ .finish()
111+ }
112+}
113+```
114+
115+ */
116+#![deny(missing_docs, missing_debug_implementations)]
117+
118+use dodrio::{builder, bumpalo, Node, Render, RenderContext};
119+use js_sys::{Object, Promise, Reflect};
120+use wasm_bindgen::prelude::*;
121+use wasm_bindgen::JsCast;
122+
123+#[wasm_bindgen]
124+extern "C" {
125+ /// A rendering component implemented in JavaScript.
126+ ///
127+ /// The rendering API is a bit duck-typed: any JS object with a `render`
128+ /// method that returns a virtual DOM as JS values with the right shape
129+ /// works.
130+ ///
131+ /// See `JsRender::new` for converting existing JS objects into `JsRender`s.
132+ #[derive(Debug, Clone)]
133+ pub type JsRender;
134+ #[wasm_bindgen(structural, method)]
135+ fn render(this: &JsRender) -> JsValue;
136+
137+ #[wasm_bindgen(extends = Object)]
138+ #[derive(Debug, Clone)]
139+ type Element;
140+ #[wasm_bindgen(structural, getter, method, js_name = tagName)]
141+ fn tag_name(this: &Element) -> String;
142+ #[wasm_bindgen(structural, getter, method)]
143+ fn listeners(this: &Element) -> js_sys::Array;
144+ #[wasm_bindgen(structural, getter, method)]
145+ fn attributes(this: &Element) -> js_sys::Array;
146+ #[wasm_bindgen(structural, getter, method)]
147+ fn children(this: &Element) -> js_sys::Array;
148+
149+ #[wasm_bindgen(extends = Object)]
150+ #[derive(Debug, Clone)]
151+ type Listener;
152+ #[wasm_bindgen(structural, getter, method)]
153+ fn on(this: &Listener) -> String;
154+ #[wasm_bindgen(structural, getter, method)]
155+ fn callback(this: &Listener) -> js_sys::Function;
156+
157+ #[wasm_bindgen(extends = Object)]
158+ #[derive(Debug, Clone)]
159+ type Attribute;
160+ #[wasm_bindgen(structural, getter, method)]
161+ fn name(this: &Attribute) -> String;
162+ #[wasm_bindgen(structural, getter, method)]
163+ fn value(this: &Attribute) -> String;
164+}
165+
166+/// A weak handle to a virtual DOM.
167+///
168+/// This is essentially the same as `dodrio::VdomWeak`, but exposed to
169+/// JavaScript.
170+#[wasm_bindgen]
171+#[derive(Clone, Debug)]
172+pub struct VdomWeak {
173+ inner: dodrio::VdomWeak,
174+}
175+
176+impl VdomWeak {
177+ fn new(inner: dodrio::VdomWeak) -> VdomWeak {
178+ VdomWeak { inner }
179+ }
180+}
181+
182+#[wasm_bindgen]
183+impl VdomWeak {
184+ /// Schedule re-rendering of the virtual DOM. A promise is returned that is
185+ /// resolved after the rendering has happened.
186+ pub fn render(&self) -> Promise {
187+ let future = self.inner.render();
188+
189+ wasm_bindgen_futures::future_to_promise(async move {
190+ if let Err(e) = future.await {
191+ let msg = e.to_string();
192+ Err(js_sys::Error::new(&msg).into())
193+ } else {
194+ Ok(JsValue::null())
195+ }
196+ })
197+ }
198+}
199+
200+impl JsRender {
201+ /// Convert a `js_sys::Object` into a `JsRender`.
202+ ///
203+ /// The given object must have a `render` method that conforms to the
204+ /// duck-typed virtual DOM interface which is described in the crate-level
205+ /// documentation.
206+ pub fn new<O>(object: O) -> JsRender
207+ where
208+ O: Into<Object>,
209+ {
210+ let object = object.into();
211+ debug_assert!(
212+ has_property(&object, "render"),
213+ "JS rendering components must have a `render` method"
214+ );
215+ object.unchecked_into::<JsRender>()
216+ }
217+}
218+
219+impl<'a> Render<'a> for JsRender {
220+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
221+ create(cx, self.render())
222+ }
223+}
224+
225+fn has_property(obj: &Object, property: &str) -> bool {
226+ Reflect::has(obj, &property.into()).unwrap_or_default()
227+}
228+
229+fn create<'a>(cx: &mut RenderContext<'a>, val: JsValue) -> Node<'a> {
230+ if let Some(txt) = val.as_string() {
231+ let text = bumpalo::collections::String::from_str_in(&txt, cx.bump);
232+ return builder::text(text.into_bump_str());
233+ }
234+
235+ let elem = val.unchecked_into::<Element>();
236+ debug_assert!(
237+ elem.is_instance_of::<Object>(),
238+ "JS render methods should only return strings for text nodes or objects for elements"
239+ );
240+ debug_assert!(
241+ has_property(&elem, "tagName"),
242+ "element objects returned by JS render methods must have a `tagName` property"
243+ );
244+
245+ let tag_name = elem.tag_name();
246+ let tag_name = bumpalo::collections::String::from_str_in(&tag_name, cx.bump);
247+
248+ builder::ElementBuilder::new(cx.bump, tag_name.into_bump_str())
249+ .listeners({
250+ let mut listeners =
251+ bumpalo::collections::Vec::new_in(cx.bump);
252+ if has_property(&elem, "listeners") {
253+ let js_listeners = elem.listeners();
254+ listeners.reserve(js_listeners.length() as usize);
255+ js_listeners.for_each(&mut |listener, _index, _array| {
256+ let listener = listener.unchecked_into::<Listener>();
257+ debug_assert!(
258+ listener.is_instance_of::<Object>(),
259+ "listeners returned by JS render methods must be objects"
260+ );
261+ debug_assert!(
262+ has_property(&listener, "on"),
263+ "listener objects returned by JS render methods must have an `on` property"
264+ );
265+ debug_assert!(
266+ has_property(&listener, "callback"),
267+ "listener objects returned by JS render methods must have an `callback` property"
268+ );
269+ let on = listener.on();
270+ let on = bumpalo::collections::String::from_str_in(&on, cx.bump);
271+ let callback = listener.callback();
272+ let elem = elem.clone();
273+ listeners.push(builder::on(cx.bump, on.into_bump_str(), move |_root, vdom, event| {
274+ let vdom = VdomWeak::new(vdom);
275+ let vdom: JsValue = vdom.into();
276+ if let Err(e) = callback.call2(&elem, &vdom, &event) {
277+ wasm_bindgen::throw_val(e);
278+ }
279+ }));
280+ });
281+ }
282+ listeners
283+ })
284+ .attributes({
285+ let mut attributes = bumpalo::collections::Vec::new_in(cx.bump);
286+ if has_property(&elem, "attributes") {
287+ let js_attributes = elem.attributes();
288+ attributes.reserve(js_attributes.length() as usize);
289+ js_attributes.for_each(&mut |attribute, _index, _array| {
290+ let attribute = attribute.unchecked_into::<Attribute>();
291+ debug_assert!(
292+ attribute.is_instance_of::<Object>(),
293+ "attributes returned by JS render methods must be objects"
294+ );
295+ debug_assert!(
296+ has_property(&attribute, "name"),
297+ "attribute objects returned by JS render methods must have a `name` property"
298+ );
299+ debug_assert!(
300+ has_property(&attribute, "value"),
301+ "attribute objects returned by JS render methods must have a `value` property"
302+ );
303+ let name = attribute.name();
304+ let name = bumpalo::collections::String::from_str_in(&name, cx.bump);
305+ let value = attribute.value();
306+ let value = bumpalo::collections::String::from_str_in(&value, cx.bump);
307+ attributes.push(builder::attr(name.into_bump_str(), value.into_bump_str()));
308+ });
309+ }
310+ attributes
311+ })
312+ .children({
313+ let mut children = bumpalo::collections::Vec::new_in(cx.bump);
314+ if has_property(&elem, "children") {
315+ let js_children = elem.children();
316+ children.reserve(js_children.length() as usize);
317+ js_children.for_each(&mut |child, _index, _array| {
318+ children.push(create(cx, child));
319+ });
320+ }
321+ children
322+ })
323+ .finish()
324+}
example/js/.gitignoredeleted−2
@@ -1,2 +0,0 @@
1−node_modules
2−dist
example/js/README.mddeleted−39
@@ -1,39 +0,0 @@
1−# create-wasm-app
2−> an npm init project for generating a project that consumes rust-generated wasm via webpack
3−
4−## Usage
5−
6−```
7−npm init wasm-app
8−```
9−
10−## What's Included
11−
12−- `.gitignore`: ignores `node_modules`
13−- `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you
14−- `README.md`: the file you are reading now!
15−- `index.html`: a bare bones html document that includes the webpack bundle
16−- `index.js`: example js file with a comment showing how to import and use a wasm pkg
17−- `package.json` and `package-lock.json`:
18− - pulls in devDependencies for using webpack:
19− - [`webpack`](https://www.npmjs.com/package/webpack)
20− - [`webpack-cli`](https://www.npmjs.com/package/webpack-cli)
21− - [`webpack-dev-server`](https://www.npmjs.com/package/webpack-dev-server)
22− - defines a `start` script to run `webpack-dev-server`
23−- `webpack.config.js`: configuration file for bundling your js with webpack
24−
25−## License
26−
27−Licensed under either of
28−
29−* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
30−* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
31−
32−at your option.
33−
34−### Contribution
35−
36−Unless you explicitly state otherwise, any contribution intentionally
37−submitted for inclusion in the work by you, as defined in the Apache-2.0
38−license, shall be dual licensed as above, without any additional terms or
39−conditions.
example/js/bootstrap.jsdeleted−5
@@ -1,5 +0,0 @@
1−// A dependency graph that contains any wasm must all be imported
2−// asynchronously. This `bootstrap.js` file does the single async import, so
3−// that no one else needs to worry about it again.
4−import("./index.js")
5− .catch(e => console.error("Error importing `index.js`:", e));
example/js/index.htmldeleted−10
@@ -1,10 +0,0 @@
1−<!DOCTYPE html>
2−<html>
3− <head>
4− <meta charset="utf-8">
5− <title>Hello wasm-pack!</title>
6− </head>
7− <body>
8− <script src="./bootstrap.js"></script>
9− </body>
10−</html>
example/js/index.jsdeleted−3
@@ -1,3 +0,0 @@
1−import { run } from "dodrio-example";
2−
3−run();
example/js/package-lock.jsondeleted−5312
@@ -1,5312 +0,0 @@
1−{
2− "name": "create-wasm-app",
3− "version": "0.1.0",
4− "lockfileVersion": 1,
5− "requires": true,
6− "dependencies": {
7− "@webassemblyjs/ast": {
8− "version": "1.7.8",
9− "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.8.tgz",
10− "integrity": "sha512-dOrtdtEyB8sInpl75yLPNksY4sRl0j/+t6aHyB/YA+ab9hV3Fo7FmG12FHzP+2MvWVAJtDb+6eXR5EZbZJ+uVg==",
11− "dev": true,
12− "requires": {
13− "@webassemblyjs/helper-module-context": "1.7.8",
14− "@webassemblyjs/helper-wasm-bytecode": "1.7.8",
15− "@webassemblyjs/wast-parser": "1.7.8"
16− }
17− },
18− "@webassemblyjs/floating-point-hex-parser": {
19− "version": "1.7.8",
20− "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.8.tgz",
21− "integrity": "sha512-kn2zNKGsbql5i56VAgRYkpG+VazqHhQQZQycT2uXAazrAEDs23gy+Odkh5VblybjnwX2/BITkDtNmSO76hdIvQ==",
22− "dev": true
23− },
24− "@webassemblyjs/helper-api-error": {
25− "version": "1.7.8",
26− "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.8.tgz",
27− "integrity": "sha512-xUwxDXsd1dUKArJEP5wWM5zxgCSwZApSOJyP1XO7M8rNUChUDblcLQ4FpzTpWG2YeylMwMl1MlP5Ztryiz1x4g==",
28− "dev": true
29− },
30− "@webassemblyjs/helper-buffer": {
31− "version": "1.7.8",
32− "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.8.tgz",
33− "integrity": "sha512-WXiIMnuvuwlhWvVOm8xEXU9DnHaa3AgAU0ZPfvY8vO1cSsmYb2WbGbHnMLgs43vXnA7XAob9b56zuZaMkxpCBg==",
34− "dev": true
35− },
36− "@webassemblyjs/helper-code-frame": {
37− "version": "1.7.8",
38− "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.8.tgz",
39− "integrity": "sha512-TLQxyD9qGOIdX5LPQOPo0Ernd88U5rHkFb8WAjeMIeA0sPjCHeVPaGqUGGIXjUcblUkjuDAc07bruCcNHUrHDA==",
40− "dev": true,
41− "requires": {
42− "@webassemblyjs/wast-printer": "1.7.8"
43− }
44− },
45− "@webassemblyjs/helper-fsm": {
46− "version": "1.7.8",
47− "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.8.tgz",
48− "integrity": "sha512-TjK0CnD8hAPkV5mbSp5aWl6SO1+H3WFcjWtixWoy8EMA99YnNzYhpc/WSYWhf7yrhpzkq5tZB0tvLK3Svr3IXA==",
49− "dev": true
50− },
51− "@webassemblyjs/helper-module-context": {
52− "version": "1.7.8",
53− "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.8.tgz",
54− "integrity": "sha512-uCutAKR7Nm0VsFixcvnB4HhAyHouNbj0Dx1p7eRjFjXGGZ+N7ftTaG1ZbWCasAEbtwGj54LP8+lkBZdTCPmLGg==",
55− "dev": true
56− },
57− "@webassemblyjs/helper-wasm-bytecode": {
58− "version": "1.7.8",
59− "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.8.tgz",
60− "integrity": "sha512-AdCCE3BMW6V34WYaKUmPgVHa88t2Z14P4/0LjLwuGkI0X6pf7nzp0CehzVVk51cKm2ymVXjl9dCG+gR1yhITIQ==",
61− "dev": true
62− },
63− "@webassemblyjs/helper-wasm-section": {
64− "version": "1.7.8",
65− "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.8.tgz",
66− "integrity": "sha512-BkBhYQuzyl4hgTGOKo87Vdw6f9nj8HhI7WYpI0MCC5qFa5ahrAPOGgyETVdnRbv+Rjukl9MxxfDmVcVC435lDg==",
67− "dev": true,
68− "requires": {
69− "@webassemblyjs/ast": "1.7.8",
70− "@webassemblyjs/helper-buffer": "1.7.8",
71− "@webassemblyjs/helper-wasm-bytecode": "1.7.8",
72− "@webassemblyjs/wasm-gen": "1.7.8"
73− }
74− },
75− "@webassemblyjs/ieee754": {
76− "version": "1.7.8",
77− "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.8.tgz",
78− "integrity": "sha512-tOarWChdG1a3y1yqCX0JMDKzrat5tQe4pV6K/TX19BcXsBLYxFQOL1DEDa5KG9syeyvCrvZ+i1+Mv1ExngvktQ==",
79− "dev": true,
80− "requires": {
81− "@xtuc/ieee754": "^1.2.0"
82− }
83− },
84− "@webassemblyjs/leb128": {
85− "version": "1.7.8",
86− "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.8.tgz",
87− "integrity": "sha512-GCYeGPgUFWJiZuP4NICbcyUQNxNLJIf476Ei+K+jVuuebtLpfvwkvYT6iTUE7oZYehhkor4Zz2g7SJ/iZaPudQ==",
88− "dev": true,
89− "requires": {
90− "@xtuc/long": "4.2.1"
91− }
92− },
93− "@webassemblyjs/utf8": {
94− "version": "1.7.8",
95− "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.8.tgz",
96− "integrity": "sha512-9X+f0VV+xNXW2ujfIRSXBJENGE6Qh7bNVKqu3yDjTFB3ar3nsThsGBBKdTG58aXOm2iUH6v28VIf88ymPXODHA==",
97− "dev": true
98− },
99− "@webassemblyjs/wasm-edit": {
100− "version": "1.7.8",
101− "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.8.tgz",
102− "integrity": "sha512-6D3Hm2gFixrfyx9XjSON4ml1FZTugqpkIz5Awvrou8fnpyprVzcm4X8pyGRtA2Piixjl3DqmX/HB1xdWyE097A==",
103− "dev": true,
104− "requires": {
105− "@webassemblyjs/ast": "1.7.8",
106− "@webassemblyjs/helper-buffer": "1.7.8",
107− "@webassemblyjs/helper-wasm-bytecode": "1.7.8",
108− "@webassemblyjs/helper-wasm-section": "1.7.8",
109− "@webassemblyjs/wasm-gen": "1.7.8",
110− "@webassemblyjs/wasm-opt": "1.7.8",
111− "@webassemblyjs/wasm-parser": "1.7.8",
112− "@webassemblyjs/wast-printer": "1.7.8"
113− }
114− },
115− "@webassemblyjs/wasm-gen": {
116− "version": "1.7.8",
117− "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.8.tgz",
118− "integrity": "sha512-a7O/wE6eBeVKKUYgpMK7NOHmMADD85rSXLe3CqrWRDwWff5y3cSVbzpN6Qv3z6C4hdkpq9qyij1Ga1kemOZGvQ==",
119− "dev": true,
120− "requires": {
121− "@webassemblyjs/ast": "1.7.8",
122− "@webassemblyjs/helper-wasm-bytecode": "1.7.8",
123− "@webassemblyjs/ieee754": "1.7.8",
124− "@webassemblyjs/leb128": "1.7.8",
125− "@webassemblyjs/utf8": "1.7.8"
126− }
127− },
128− "@webassemblyjs/wasm-opt": {
129− "version": "1.7.8",
130− "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.8.tgz",
131− "integrity": "sha512-3lbQ0PT81NHCdi1sR/7+SNpZadM4qYcTSr62nFFAA7e5lFwJr14M1Gi+A/Y3PgcDWOHYjsaNGPpPU0H03N6Blg==",
132− "dev": true,
133− "requires": {
134− "@webassemblyjs/ast": "1.7.8",
135− "@webassemblyjs/helper-buffer": "1.7.8",
136− "@webassemblyjs/wasm-gen": "1.7.8",
137− "@webassemblyjs/wasm-parser": "1.7.8"
138− }
139− },
140− "@webassemblyjs/wasm-parser": {
141− "version": "1.7.8",
142− "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.8.tgz",
143− "integrity": "sha512-rZ/zlhp9DHR/05zh1MbAjT2t624sjrPP/OkJCjXqzm7ynH+nIdNcn9Ixc+qzPMFXhIrk0rBoQ3to6sEIvHh9jQ==",
144− "dev": true,
145− "requires": {
146− "@webassemblyjs/ast": "1.7.8",
147− "@webassemblyjs/helper-api-error": "1.7.8",
148− "@webassemblyjs/helper-wasm-bytecode": "1.7.8",
149− "@webassemblyjs/ieee754": "1.7.8",
150− "@webassemblyjs/leb128": "1.7.8",
151− "@webassemblyjs/utf8": "1.7.8"
152− }
153− },
154− "@webassemblyjs/wast-parser": {
155− "version": "1.7.8",
156− "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.8.tgz",
157− "integrity": "sha512-Q/zrvtUvzWuSiJMcSp90fi6gp2nraiHXjTV2VgAluVdVapM4gy1MQn7akja2p6eSBDQpKJPJ6P4TxRkghRS5dg==",
158− "dev": true,
159− "requires": {
160− "@webassemblyjs/ast": "1.7.8",
161− "@webassemblyjs/floating-point-hex-parser": "1.7.8",
162− "@webassemblyjs/helper-api-error": "1.7.8",
163− "@webassemblyjs/helper-code-frame": "1.7.8",
164− "@webassemblyjs/helper-fsm": "1.7.8",
165− "@xtuc/long": "4.2.1"
166− }
167− },
168− "@webassemblyjs/wast-printer": {
169− "version": "1.7.8",
170− "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.8.tgz",
171− "integrity": "sha512-GllIthRtwTxRDAURRNXscu7Napzmdf1jt1gpiZiK/QN4fH0lSGs3OTmvdfsMNP7tqI4B3ZtfaaWRlNIQug6Xyg==",
172− "dev": true,
173− "requires": {
174− "@webassemblyjs/ast": "1.7.8",
175− "@webassemblyjs/wast-parser": "1.7.8",
176− "@xtuc/long": "4.2.1"
177− }
178− },
179− "@xtuc/ieee754": {
180− "version": "1.2.0",
181− "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
182− "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
183− "dev": true
184− },
185− "@xtuc/long": {
186− "version": "4.2.1",
187− "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.1.tgz",
188− "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==",
189− "dev": true
190− },
191− "accepts": {
192− "version": "1.3.5",
193− "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
194− "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
195− "dev": true,
196− "requires": {
197− "mime-types": "~2.1.18",
198− "negotiator": "0.6.1"
199− }
200− },
201− "acorn": {
202− "version": "5.7.3",
203− "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
204− "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
205− "dev": true
206− },
207− "acorn-dynamic-import": {
208− "version": "3.0.0",
209− "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz",
210− "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==",
211− "dev": true,
212− "requires": {
213− "acorn": "^5.0.0"
214− }
215− },
216− "ajv": {
217− "version": "6.5.3",
218− "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz",
219− "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==",
220− "dev": true,
221− "requires": {
222− "fast-deep-equal": "^2.0.1",
223− "fast-json-stable-stringify": "^2.0.0",
224− "json-schema-traverse": "^0.4.1",
225− "uri-js": "^4.2.2"
226− }
227− },
228− "ajv-errors": {
229− "version": "1.0.0",
230− "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz",
231− "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=",
232− "dev": true
233− },
234− "ajv-keywords": {
235− "version": "3.2.0",
236− "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz",
237− "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
238− "dev": true
239− },
240− "ansi-colors": {
241− "version": "3.2.1",
242− "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.1.tgz",
243− "integrity": "sha512-Xt+zb6nqgvV9SWAVp0EG3lRsHcbq5DDgqjPPz6pwgtj6RKz65zGXMNa82oJfOSBA/to6GmRP7Dr+6o+kbApTzQ==",
244− "dev": true
245− },
246− "ansi-html": {
247− "version": "0.0.7",
248− "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
249− "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
250− "dev": true
251− },
252− "ansi-regex": {
253− "version": "3.0.0",
254− "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
255− "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
256− "dev": true
257− },
258− "ansi-styles": {
259− "version": "3.2.1",
260− "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
261− "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
262− "dev": true,
263− "requires": {
264− "color-convert": "^1.9.0"
265− }
266− },
267− "anymatch": {
268− "version": "2.0.0",
269− "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
270− "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
271− "dev": true,
272− "requires": {
273− "micromatch": "^3.1.4",
274− "normalize-path": "^2.1.1"
275− }
276− },
277− "aproba": {
278− "version": "1.2.0",
279− "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
280− "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
281− "dev": true
282− },
283− "arr-diff": {
284− "version": "4.0.0",
285− "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
286− "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
287− "dev": true
288− },
289− "arr-flatten": {
290− "version": "1.1.0",
291− "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
292− "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
293− "dev": true
294− },
295− "arr-union": {
296− "version": "3.1.0",
297− "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
298− "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
299− "dev": true
300− },
301− "array-flatten": {
302− "version": "2.1.1",
303− "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
304− "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=",
305− "dev": true
306− },
307− "array-union": {
308− "version": "1.0.2",
309− "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
310− "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
311− "dev": true,
312− "requires": {
313− "array-uniq": "^1.0.1"
314− }
315− },
316− "array-uniq": {
317− "version": "1.0.3",
318− "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
319− "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
320− "dev": true
321− },
322− "array-unique": {
323− "version": "0.3.2",
324− "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
325− "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
326− "dev": true
327− },
328− "arrify": {
329− "version": "1.0.1",
330− "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
331− "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
332− "dev": true
333− },
334− "asn1.js": {
335− "version": "4.10.1",
336− "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
337− "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
338− "dev": true,
339− "requires": {
340− "bn.js": "^4.0.0",
341− "inherits": "^2.0.1",
342− "minimalistic-assert": "^1.0.0"
343− }
344− },
345− "assert": {
346− "version": "1.4.1",
347− "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
348− "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
349− "dev": true,
350− "requires": {
351− "util": "0.10.3"
352− },
353− "dependencies": {
354− "inherits": {
355− "version": "2.0.1",
356− "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
357− "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
358− "dev": true
359− },
360− "util": {
361− "version": "0.10.3",
362− "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
363− "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
364− "dev": true,
365− "requires": {
366− "inherits": "2.0.1"
367− }
368− }
369− }
370− },
371− "assign-symbols": {
372− "version": "1.0.0",
373− "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
374− "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
375− "dev": true
376− },
377− "async": {
378− "version": "1.5.2",
379− "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
380− "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
381− "dev": true
382− },
383− "async-each": {
384− "version": "1.0.1",
385− "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
386− "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
387− "dev": true
388− },
389− "atob": {
390− "version": "2.1.1",
391− "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz",
392− "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=",
393− "dev": true
394− },
395− "balanced-match": {
396− "version": "1.0.0",
397− "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
398− "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
399− "dev": true
400− },
401− "base": {
402− "version": "0.11.2",
403− "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
404− "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
405− "dev": true,
406− "requires": {
407− "cache-base": "^1.0.1",
408− "class-utils": "^0.3.5",
409− "component-emitter": "^1.2.1",
410− "define-property": "^1.0.0",
411− "isobject": "^3.0.1",
412− "mixin-deep": "^1.2.0",
413− "pascalcase": "^0.1.1"
414− },
415− "dependencies": {
416− "define-property": {
417− "version": "1.0.0",
418− "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
419− "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
420− "dev": true,
421− "requires": {
422− "is-descriptor": "^1.0.0"
423− }
424− },
425− "is-accessor-descriptor": {
426− "version": "1.0.0",
427− "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
428− "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
429− "dev": true,
430− "requires": {
431− "kind-of": "^6.0.0"
432− }
433− },
434− "is-data-descriptor": {
435− "version": "1.0.0",
436− "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
437− "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
438− "dev": true,
439− "requires": {
440− "kind-of": "^6.0.0"
441− }
442− },
443− "is-descriptor": {
444− "version": "1.0.2",
445− "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
446− "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
447− "dev": true,
448− "requires": {
449− "is-accessor-descriptor": "^1.0.0",
450− "is-data-descriptor": "^1.0.0",
451− "kind-of": "^6.0.2"
452− }
453− }
454− }
455− },
456− "base64-js": {
457− "version": "1.3.0",
458− "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
459− "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==",
460− "dev": true
461− },
462− "batch": {
463− "version": "0.6.1",
464− "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
465− "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
466− "dev": true
467− },
468− "big.js": {
469− "version": "3.2.0",
470− "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz",
471− "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==",
472− "dev": true
473− },
474− "binary-extensions": {
475− "version": "1.11.0",
476− "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz",
477− "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=",
478− "dev": true
479− },
480− "bluebird": {
481− "version": "3.5.1",
482− "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
483− "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
484− "dev": true
485− },
486− "bn.js": {
487− "version": "4.11.8",
488− "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
489− "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
490− "dev": true
491− },
492− "body-parser": {
493− "version": "1.18.3",
494− "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
495− "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
496− "dev": true,
497− "requires": {
498− "bytes": "3.0.0",
499− "content-type": "~1.0.4",
500− "debug": "2.6.9",
501− "depd": "~1.1.2",
502− "http-errors": "~1.6.3",
503− "iconv-lite": "0.4.23",
504− "on-finished": "~2.3.0",
505− "qs": "6.5.2",
506− "raw-body": "2.3.3",
507− "type-is": "~1.6.16"
508− },
509− "dependencies": {
510− "debug": {
511− "version": "2.6.9",
512− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
513− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
514− "dev": true,
515− "requires": {
516− "ms": "2.0.0"
517− }
518− }
519− }
520− },
521− "bonjour": {
522− "version": "3.5.0",
523− "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
524− "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
525− "dev": true,
526− "requires": {
527− "array-flatten": "^2.1.0",
528− "deep-equal": "^1.0.1",
529− "dns-equal": "^1.0.0",
530− "dns-txt": "^2.0.2",
531− "multicast-dns": "^6.0.1",
532− "multicast-dns-service-types": "^1.1.0"
533− }
534− },
535− "brace-expansion": {
536− "version": "1.1.11",
537− "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
538− "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
539− "dev": true,
540− "requires": {
541− "balanced-match": "^1.0.0",
542− "concat-map": "0.0.1"
543− }
544− },
545− "braces": {
546− "version": "2.3.2",
547− "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
548− "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
549− "dev": true,
550− "requires": {
551− "arr-flatten": "^1.1.0",
552− "array-unique": "^0.3.2",
553− "extend-shallow": "^2.0.1",
554− "fill-range": "^4.0.0",
555− "isobject": "^3.0.1",
556− "repeat-element": "^1.1.2",
557− "snapdragon": "^0.8.1",
558− "snapdragon-node": "^2.0.1",
559− "split-string": "^3.0.2",
560− "to-regex": "^3.0.1"
561− },
562− "dependencies": {
563− "extend-shallow": {
564− "version": "2.0.1",
565− "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
566− "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
567− "dev": true,
568− "requires": {
569− "is-extendable": "^0.1.0"
570− }
571− }
572− }
573− },
574− "brorand": {
575− "version": "1.1.0",
576− "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
577− "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
578− "dev": true
579− },
580− "browserify-aes": {
581− "version": "1.2.0",
582− "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
583− "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
584− "dev": true,
585− "requires": {
586− "buffer-xor": "^1.0.3",
587− "cipher-base": "^1.0.0",
588− "create-hash": "^1.1.0",
589− "evp_bytestokey": "^1.0.3",
590− "inherits": "^2.0.1",
591− "safe-buffer": "^5.0.1"
592− }
593− },
594− "browserify-cipher": {
595− "version": "1.0.1",
596− "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
597− "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
598− "dev": true,
599− "requires": {
600− "browserify-aes": "^1.0.4",
601− "browserify-des": "^1.0.0",
602− "evp_bytestokey": "^1.0.0"
603− }
604− },
605− "browserify-des": {
606− "version": "1.0.2",
607− "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
608− "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
609− "dev": true,
610− "requires": {
611− "cipher-base": "^1.0.1",
612− "des.js": "^1.0.0",
613− "inherits": "^2.0.1",
614− "safe-buffer": "^5.1.2"
615− }
616− },
617− "browserify-rsa": {
618− "version": "4.0.1",
619− "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
620− "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
621− "dev": true,
622− "requires": {
623− "bn.js": "^4.1.0",
624− "randombytes": "^2.0.1"
625− }
626− },
627− "browserify-sign": {
628− "version": "4.0.4",
629− "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
630− "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
631− "dev": true,
632− "requires": {
633− "bn.js": "^4.1.1",
634− "browserify-rsa": "^4.0.0",
635− "create-hash": "^1.1.0",
636− "create-hmac": "^1.1.2",
637− "elliptic": "^6.0.0",
638− "inherits": "^2.0.1",
639− "parse-asn1": "^5.0.0"
640− }
641− },
642− "browserify-zlib": {
643− "version": "0.2.0",
644− "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
645− "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
646− "dev": true,
647− "requires": {
648− "pako": "~1.0.5"
649− }
650− },
651− "buffer": {
652− "version": "4.9.1",
653− "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
654− "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
655− "dev": true,
656− "requires": {
657− "base64-js": "^1.0.2",
658− "ieee754": "^1.1.4",
659− "isarray": "^1.0.0"
660− }
661− },
662− "buffer-from": {
663− "version": "1.1.1",
664− "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
665− "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
666− "dev": true
667− },
668− "buffer-indexof": {
669− "version": "1.1.1",
670− "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
671− "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
672− "dev": true
673− },
674− "buffer-xor": {
675− "version": "1.0.3",
676− "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
677− "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
678− "dev": true
679− },
680− "builtin-status-codes": {
681− "version": "3.0.0",
682− "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
683− "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
684− "dev": true
685− },
686− "bytes": {
687− "version": "3.0.0",
688− "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
689− "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
690− "dev": true
691− },
692− "cacache": {
693− "version": "10.0.4",
694− "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
695− "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==",
696− "dev": true,
697− "requires": {
698− "bluebird": "^3.5.1",
699− "chownr": "^1.0.1",
700− "glob": "^7.1.2",
701− "graceful-fs": "^4.1.11",
702− "lru-cache": "^4.1.1",
703− "mississippi": "^2.0.0",
704− "mkdirp": "^0.5.1",
705− "move-concurrently": "^1.0.1",
706− "promise-inflight": "^1.0.1",
707− "rimraf": "^2.6.2",
708− "ssri": "^5.2.4",
709− "unique-filename": "^1.1.0",
710− "y18n": "^4.0.0"
711− }
712− },
713− "cache-base": {
714− "version": "1.0.1",
715− "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
716− "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
717− "dev": true,
718− "requires": {
719− "collection-visit": "^1.0.0",
720− "component-emitter": "^1.2.1",
721− "get-value": "^2.0.6",
722− "has-value": "^1.0.0",
723− "isobject": "^3.0.1",
724− "set-value": "^2.0.0",
725− "to-object-path": "^0.3.0",
726− "union-value": "^1.0.0",
727− "unset-value": "^1.0.0"
728− }
729− },
730− "camelcase": {
731− "version": "4.1.0",
732− "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
733− "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
734− "dev": true
735− },
736− "chalk": {
737− "version": "2.4.1",
738− "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
739− "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
740− "dev": true,
741− "requires": {
742− "ansi-styles": "^3.2.1",
743− "escape-string-regexp": "^1.0.5",
744− "supports-color": "^5.3.0"
745− }
746− },
747− "chokidar": {
748− "version": "2.0.4",
749− "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
750− "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==",
751− "dev": true,
752− "requires": {
753− "anymatch": "^2.0.0",
754− "async-each": "^1.0.0",
755− "braces": "^2.3.0",
756− "fsevents": "^1.2.2",
757− "glob-parent": "^3.1.0",
758− "inherits": "^2.0.1",
759− "is-binary-path": "^1.0.0",
760− "is-glob": "^4.0.0",
761− "lodash.debounce": "^4.0.8",
762− "normalize-path": "^2.1.1",
763− "path-is-absolute": "^1.0.0",
764− "readdirp": "^2.0.0",
765− "upath": "^1.0.5"
766− }
767− },
768− "chownr": {
769− "version": "1.0.1",
770− "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
771− "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=",
772− "dev": true
773− },
774− "chrome-trace-event": {
775− "version": "1.0.0",
776− "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz",
777− "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==",
778− "dev": true,
779− "requires": {
780− "tslib": "^1.9.0"
781− }
782− },
783− "cipher-base": {
784− "version": "1.0.4",
785− "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
786− "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
787− "dev": true,
788− "requires": {
789− "inherits": "^2.0.1",
790− "safe-buffer": "^5.0.1"
791− }
792− },
793− "class-utils": {
794− "version": "0.3.6",
795− "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
796− "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
797− "dev": true,
798− "requires": {
799− "arr-union": "^3.1.0",
800− "define-property": "^0.2.5",
801− "isobject": "^3.0.0",
802− "static-extend": "^0.1.1"
803− },
804− "dependencies": {
805− "define-property": {
806− "version": "0.2.5",
807− "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
808− "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
809− "dev": true,
810− "requires": {
811− "is-descriptor": "^0.1.0"
812− }
813− }
814− }
815− },
816− "cliui": {
817− "version": "4.1.0",
818− "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
819− "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
820− "dev": true,
821− "requires": {
822− "string-width": "^2.1.1",
823− "strip-ansi": "^4.0.0",
824− "wrap-ansi": "^2.0.0"
825− }
826− },
827− "code-point-at": {
828− "version": "1.1.0",
829− "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
830− "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
831− "dev": true
832− },
833− "collection-visit": {
834− "version": "1.0.0",
835− "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
836− "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
837− "dev": true,
838− "requires": {
839− "map-visit": "^1.0.0",
840− "object-visit": "^1.0.0"
841− }
842− },
843− "color-convert": {
844− "version": "1.9.3",
845− "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
846− "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
847− "dev": true,
848− "requires": {
849− "color-name": "1.1.3"
850− }
851− },
852− "color-name": {
853− "version": "1.1.3",
854− "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
855− "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
856− "dev": true
857− },
858− "commander": {
859− "version": "2.13.0",
860− "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
861− "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
862− "dev": true
863− },
864− "commondir": {
865− "version": "1.0.1",
866− "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
867− "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
868− "dev": true
869− },
870− "component-emitter": {
871− "version": "1.2.1",
872− "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
873− "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
874− "dev": true
875− },
876− "compressible": {
877− "version": "2.0.15",
878− "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz",
879− "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==",
880− "dev": true,
881− "requires": {
882− "mime-db": ">= 1.36.0 < 2"
883− }
884− },
885− "compression": {
886− "version": "1.7.3",
887− "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz",
888− "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==",
889− "dev": true,
890− "requires": {
891− "accepts": "~1.3.5",
892− "bytes": "3.0.0",
893− "compressible": "~2.0.14",
894− "debug": "2.6.9",
895− "on-headers": "~1.0.1",
896− "safe-buffer": "5.1.2",
897− "vary": "~1.1.2"
898− },
899− "dependencies": {
900− "debug": {
901− "version": "2.6.9",
902− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
903− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
904− "dev": true,
905− "requires": {
906− "ms": "2.0.0"
907− }
908− }
909− }
910− },
911− "concat-map": {
912− "version": "0.0.1",
913− "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
914− "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
915− "dev": true
916− },
917− "concat-stream": {
918− "version": "1.6.2",
919− "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
920− "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
921− "dev": true,
922− "requires": {
923− "buffer-from": "^1.0.0",
924− "inherits": "^2.0.3",
925− "readable-stream": "^2.2.2",
926− "typedarray": "^0.0.6"
927− }
928− },
929− "connect-history-api-fallback": {
930− "version": "1.5.0",
931− "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz",
932− "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=",
933− "dev": true
934− },
935− "console-browserify": {
936− "version": "1.1.0",
937− "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
938− "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
939− "dev": true,
940− "requires": {
941− "date-now": "^0.1.4"
942− }
943− },
944− "constants-browserify": {
945− "version": "1.0.0",
946− "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
947− "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
948− "dev": true
949− },
950− "content-disposition": {
951− "version": "0.5.2",
952− "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
953− "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
954− "dev": true
955− },
956− "content-type": {
957− "version": "1.0.4",
958− "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
959− "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
960− "dev": true
961− },
962− "cookie": {
963− "version": "0.3.1",
964− "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
965− "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
966− "dev": true
967− },
968− "cookie-signature": {
969− "version": "1.0.6",
970− "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
971− "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
972− "dev": true
973− },
974− "copy-concurrently": {
975− "version": "1.0.5",
976− "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
977− "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
978− "dev": true,
979− "requires": {
980− "aproba": "^1.1.1",
981− "fs-write-stream-atomic": "^1.0.8",
982− "iferr": "^0.1.5",
983− "mkdirp": "^0.5.1",
984− "rimraf": "^2.5.4",
985− "run-queue": "^1.0.0"
986− }
987− },
988− "copy-descriptor": {
989− "version": "0.1.1",
990− "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
991− "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
992− "dev": true
993− },
994− "copy-webpack-plugin": {
995− "version": "4.5.4",
996− "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.4.tgz",
997− "integrity": "sha512-0lstlEyj74OAtYMrDxlNZsU7cwFijAI3Ofz2fD6Mpo9r4xCv4yegfa3uHIKvZY1NSuOtE9nvG6TAhJ+uz9gDaQ==",
998− "dev": true,
999− "requires": {
1000− "cacache": "^10.0.4",
1001− "find-cache-dir": "^1.0.0",
1002− "globby": "^7.1.1",
1003− "is-glob": "^4.0.0",
1004− "loader-utils": "^1.1.0",
1005− "minimatch": "^3.0.4",
1006− "p-limit": "^1.0.0",
1007− "serialize-javascript": "^1.4.0"
1008− },
1009− "dependencies": {
1010− "globby": {
1011− "version": "7.1.1",
1012− "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
1013− "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
1014− "dev": true,
1015− "requires": {
1016− "array-union": "^1.0.1",
1017− "dir-glob": "^2.0.0",
1018− "glob": "^7.1.2",
1019− "ignore": "^3.3.5",
1020− "pify": "^3.0.0",
1021− "slash": "^1.0.0"
1022− }
1023− }
1024− }
1025− },
1026− "core-util-is": {
1027− "version": "1.0.2",
1028− "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
1029− "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
1030− "dev": true
1031− },
1032− "create-ecdh": {
1033− "version": "4.0.3",
1034− "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
1035− "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
1036− "dev": true,
1037− "requires": {
1038− "bn.js": "^4.1.0",
1039− "elliptic": "^6.0.0"
1040− }
1041− },
1042− "create-hash": {
1043− "version": "1.2.0",
1044− "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
1045− "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
1046− "dev": true,
1047− "requires": {
1048− "cipher-base": "^1.0.1",
1049− "inherits": "^2.0.1",
1050− "md5.js": "^1.3.4",
1051− "ripemd160": "^2.0.1",
1052− "sha.js": "^2.4.0"
1053− }
1054− },
1055− "create-hmac": {
1056− "version": "1.1.7",
1057− "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
1058− "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
1059− "dev": true,
1060− "requires": {
1061− "cipher-base": "^1.0.3",
1062− "create-hash": "^1.1.0",
1063− "inherits": "^2.0.1",
1064− "ripemd160": "^2.0.0",
1065− "safe-buffer": "^5.0.1",
1066− "sha.js": "^2.4.8"
1067− }
1068− },
1069− "cross-spawn": {
1070− "version": "6.0.5",
1071− "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
1072− "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
1073− "dev": true,
1074− "requires": {
1075− "nice-try": "^1.0.4",
1076− "path-key": "^2.0.1",
1077− "semver": "^5.5.0",
1078− "shebang-command": "^1.2.0",
1079− "which": "^1.2.9"
1080− }
1081− },
1082− "crypto-browserify": {
1083− "version": "3.12.0",
1084− "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
1085− "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
1086− "dev": true,
1087− "requires": {
1088− "browserify-cipher": "^1.0.0",
1089− "browserify-sign": "^4.0.0",
1090− "create-ecdh": "^4.0.0",
1091− "create-hash": "^1.1.0",
1092− "create-hmac": "^1.1.0",
1093− "diffie-hellman": "^5.0.0",
1094− "inherits": "^2.0.1",
1095− "pbkdf2": "^3.0.3",
1096− "public-encrypt": "^4.0.0",
1097− "randombytes": "^2.0.0",
1098− "randomfill": "^1.0.3"
1099− }
1100− },
1101− "cyclist": {
1102− "version": "0.2.2",
1103− "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
1104− "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
1105− "dev": true
1106− },
1107− "date-now": {
1108− "version": "0.1.4",
1109− "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
1110− "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
1111− "dev": true
1112− },
1113− "debug": {
1114− "version": "3.2.6",
1115− "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
1116− "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
1117− "dev": true,
1118− "requires": {
1119− "ms": "^2.1.1"
1120− },
1121− "dependencies": {
1122− "ms": {
1123− "version": "2.1.1",
1124− "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
1125− "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
1126− "dev": true
1127− }
1128− }
1129− },
1130− "decamelize": {
1131− "version": "2.0.0",
1132− "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz",
1133− "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==",
1134− "dev": true,
1135− "requires": {
1136− "xregexp": "4.0.0"
1137− }
1138− },
1139− "decode-uri-component": {
1140− "version": "0.2.0",
1141− "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
1142− "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
1143− "dev": true
1144− },
1145− "deep-equal": {
1146− "version": "1.0.1",
1147− "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
1148− "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
1149− "dev": true
1150− },
1151− "default-gateway": {
1152− "version": "2.7.2",
1153− "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz",
1154− "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==",
1155− "dev": true,
1156− "requires": {
1157− "execa": "^0.10.0",
1158− "ip-regex": "^2.1.0"
1159− }
1160− },
1161− "define-property": {
1162− "version": "2.0.2",
1163− "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
1164− "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
1165− "dev": true,
1166− "requires": {
1167− "is-descriptor": "^1.0.2",
1168− "isobject": "^3.0.1"
1169− },
1170− "dependencies": {
1171− "is-accessor-descriptor": {
1172− "version": "1.0.0",
1173− "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
1174− "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
1175− "dev": true,
1176− "requires": {
1177− "kind-of": "^6.0.0"
1178− }
1179− },
1180− "is-data-descriptor": {
1181− "version": "1.0.0",
1182− "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
1183− "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
1184− "dev": true,
1185− "requires": {
1186− "kind-of": "^6.0.0"
1187− }
1188− },
1189− "is-descriptor": {
1190− "version": "1.0.2",
1191− "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
1192− "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
1193− "dev": true,
1194− "requires": {
1195− "is-accessor-descriptor": "^1.0.0",
1196− "is-data-descriptor": "^1.0.0",
1197− "kind-of": "^6.0.2"
1198− }
1199− }
1200− }
1201− },
1202− "del": {
1203− "version": "3.0.0",
1204− "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz",
1205− "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=",
1206− "dev": true,
1207− "requires": {
1208− "globby": "^6.1.0",
1209− "is-path-cwd": "^1.0.0",
1210− "is-path-in-cwd": "^1.0.0",
1211− "p-map": "^1.1.1",
1212− "pify": "^3.0.0",
1213− "rimraf": "^2.2.8"
1214− }
1215− },
1216− "depd": {
1217− "version": "1.1.2",
1218− "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
1219− "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
1220− "dev": true
1221− },
1222− "des.js": {
1223− "version": "1.0.0",
1224− "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
1225− "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
1226− "dev": true,
1227− "requires": {
1228− "inherits": "^2.0.1",
1229− "minimalistic-assert": "^1.0.0"
1230− }
1231− },
1232− "destroy": {
1233− "version": "1.0.4",
1234− "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
1235− "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
1236− "dev": true
1237− },
1238− "detect-node": {
1239− "version": "2.0.4",
1240− "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
1241− "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
1242− "dev": true
1243− },
1244− "diffie-hellman": {
1245− "version": "5.0.3",
1246− "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
1247− "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
1248− "dev": true,
1249− "requires": {
1250− "bn.js": "^4.1.0",
1251− "miller-rabin": "^4.0.0",
1252− "randombytes": "^2.0.0"
1253− }
1254− },
1255− "dir-glob": {
1256− "version": "2.0.0",
1257− "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz",
1258− "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==",
1259− "dev": true,
1260− "requires": {
1261− "arrify": "^1.0.1",
1262− "path-type": "^3.0.0"
1263− }
1264− },
1265− "dns-equal": {
1266− "version": "1.0.0",
1267− "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
1268− "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
1269− "dev": true
1270− },
1271− "dns-packet": {
1272− "version": "1.3.1",
1273− "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
1274− "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
1275− "dev": true,
1276− "requires": {
1277− "ip": "^1.1.0",
1278− "safe-buffer": "^5.0.1"
1279− }
1280− },
1281− "dns-txt": {
1282− "version": "2.0.2",
1283− "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
1284− "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
1285− "dev": true,
1286− "requires": {
1287− "buffer-indexof": "^1.0.0"
1288− }
1289− },
1290− "domain-browser": {
1291− "version": "1.2.0",
1292− "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
1293− "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
1294− "dev": true
1295− },
1296− "duplexify": {
1297− "version": "3.6.0",
1298− "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz",
1299− "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==",
1300− "dev": true,
1301− "requires": {
1302− "end-of-stream": "^1.0.0",
1303− "inherits": "^2.0.1",
1304− "readable-stream": "^2.0.0",
1305− "stream-shift": "^1.0.0"
1306− }
1307− },
1308− "ee-first": {
1309− "version": "1.1.1",
1310− "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
1311− "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
1312− "dev": true
1313− },
1314− "elliptic": {
1315− "version": "6.4.1",
1316− "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
1317− "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
1318− "dev": true,
1319− "requires": {
1320− "bn.js": "^4.4.0",
1321− "brorand": "^1.0.1",
1322− "hash.js": "^1.0.0",
1323− "hmac-drbg": "^1.0.0",
1324− "inherits": "^2.0.1",
1325− "minimalistic-assert": "^1.0.0",
1326− "minimalistic-crypto-utils": "^1.0.0"
1327− }
1328− },
1329− "emojis-list": {
1330− "version": "2.1.0",
1331− "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
1332− "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
1333− "dev": true
1334− },
1335− "encodeurl": {
1336− "version": "1.0.2",
1337− "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1338− "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
1339− "dev": true
1340− },
1341− "end-of-stream": {
1342− "version": "1.4.1",
1343− "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
1344− "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
1345− "dev": true,
1346− "requires": {
1347− "once": "^1.4.0"
1348− }
1349− },
1350− "enhanced-resolve": {
1351− "version": "4.1.0",
1352− "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
1353− "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
1354− "dev": true,
1355− "requires": {
1356− "graceful-fs": "^4.1.2",
1357− "memory-fs": "^0.4.0",
1358− "tapable": "^1.0.0"
1359− }
1360− },
1361− "errno": {
1362− "version": "0.1.7",
1363− "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
1364− "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
1365− "dev": true,
1366− "requires": {
1367− "prr": "~1.0.1"
1368− }
1369− },
1370− "escape-html": {
1371− "version": "1.0.3",
1372− "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
1373− "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
1374− "dev": true
1375− },
1376− "escape-string-regexp": {
1377− "version": "1.0.5",
1378− "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
1379− "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
1380− "dev": true
1381− },
1382− "eslint-scope": {
1383− "version": "4.0.0",
1384− "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz",
1385− "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==",
1386− "dev": true,
1387− "requires": {
1388− "esrecurse": "^4.1.0",
1389− "estraverse": "^4.1.1"
1390− }
1391− },
1392− "esrecurse": {
1393− "version": "4.2.1",
1394− "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
1395− "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
1396− "dev": true,
1397− "requires": {
1398− "estraverse": "^4.1.0"
1399− }
1400− },
1401− "estraverse": {
1402− "version": "4.2.0",
1403− "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
1404− "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
1405− "dev": true
1406− },
1407− "etag": {
1408− "version": "1.8.1",
1409− "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
1410− "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
1411− "dev": true
1412− },
1413− "eventemitter3": {
1414− "version": "3.1.0",
1415− "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
1416− "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
1417− "dev": true
1418− },
1419− "events": {
1420− "version": "1.1.1",
1421− "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz",
1422− "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
1423− "dev": true
1424− },
1425− "eventsource": {
1426− "version": "1.0.7",
1427− "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
1428− "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
1429− "dev": true,
1430− "requires": {
1431− "original": "^1.0.0"
1432− }
1433− },
1434− "evp_bytestokey": {
1435− "version": "1.0.3",
1436− "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
1437− "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
1438− "dev": true,
1439− "requires": {
1440− "md5.js": "^1.3.4",
1441− "safe-buffer": "^5.1.1"
1442− }
1443− },
1444− "execa": {
1445− "version": "0.10.0",
1446− "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz",
1447− "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==",
1448− "dev": true,
1449− "requires": {
1450− "cross-spawn": "^6.0.0",
1451− "get-stream": "^3.0.0",
1452− "is-stream": "^1.1.0",
1453− "npm-run-path": "^2.0.0",
1454− "p-finally": "^1.0.0",
1455− "signal-exit": "^3.0.0",
1456− "strip-eof": "^1.0.0"
1457− }
1458− },
1459− "expand-brackets": {
1460− "version": "2.1.4",
1461− "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
1462− "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
1463− "dev": true,
1464− "requires": {
1465− "debug": "^2.3.3",
1466− "define-property": "^0.2.5",
1467− "extend-shallow": "^2.0.1",
1468− "posix-character-classes": "^0.1.0",
1469− "regex-not": "^1.0.0",
1470− "snapdragon": "^0.8.1",
1471− "to-regex": "^3.0.1"
1472− },
1473− "dependencies": {
1474− "debug": {
1475− "version": "2.6.9",
1476− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
1477− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
1478− "dev": true,
1479− "requires": {
1480− "ms": "2.0.0"
1481− }
1482− },
1483− "define-property": {
1484− "version": "0.2.5",
1485− "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
1486− "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
1487− "dev": true,
1488− "requires": {
1489− "is-descriptor": "^0.1.0"
1490− }
1491− },
1492− "extend-shallow": {
1493− "version": "2.0.1",
1494− "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
1495− "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
1496− "dev": true,
1497− "requires": {
1498− "is-extendable": "^0.1.0"
1499− }
1500− }
1501− }
1502− },
1503− "express": {
1504− "version": "4.16.4",
1505− "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
1506− "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
1507− "dev": true,
1508− "requires": {
1509− "accepts": "~1.3.5",
1510− "array-flatten": "1.1.1",
1511− "body-parser": "1.18.3",
1512− "content-disposition": "0.5.2",
1513− "content-type": "~1.0.4",
1514− "cookie": "0.3.1",
1515− "cookie-signature": "1.0.6",
1516− "debug": "2.6.9",
1517− "depd": "~1.1.2",
1518− "encodeurl": "~1.0.2",
1519− "escape-html": "~1.0.3",
1520− "etag": "~1.8.1",
1521− "finalhandler": "1.1.1",
1522− "fresh": "0.5.2",
1523− "merge-descriptors": "1.0.1",
1524− "methods": "~1.1.2",
1525− "on-finished": "~2.3.0",
1526− "parseurl": "~1.3.2",
1527− "path-to-regexp": "0.1.7",
1528− "proxy-addr": "~2.0.4",
1529− "qs": "6.5.2",
1530− "range-parser": "~1.2.0",
1531− "safe-buffer": "5.1.2",
1532− "send": "0.16.2",
1533− "serve-static": "1.13.2",
1534− "setprototypeof": "1.1.0",
1535− "statuses": "~1.4.0",
1536− "type-is": "~1.6.16",
1537− "utils-merge": "1.0.1",
1538− "vary": "~1.1.2"
1539− },
1540− "dependencies": {
1541− "array-flatten": {
1542− "version": "1.1.1",
1543− "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
1544− "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
1545− "dev": true
1546− },
1547− "debug": {
1548− "version": "2.6.9",
1549− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
1550− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
1551− "dev": true,
1552− "requires": {
1553− "ms": "2.0.0"
1554− }
1555− }
1556− }
1557− },
1558− "extend-shallow": {
1559− "version": "3.0.2",
1560− "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
1561− "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
1562− "dev": true,
1563− "requires": {
1564− "assign-symbols": "^1.0.0",
1565− "is-extendable": "^1.0.1"
1566− },
1567− "dependencies": {
1568− "is-extendable": {
1569− "version": "1.0.1",
1570− "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
1571− "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
1572− "dev": true,
1573− "requires": {
1574− "is-plain-object": "^2.0.4"
1575− }
1576− }
1577− }
1578− },
1579− "extglob": {
1580− "version": "2.0.4",
1581− "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
1582− "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
1583− "dev": true,
1584− "requires": {
1585− "array-unique": "^0.3.2",
1586− "define-property": "^1.0.0",
1587− "expand-brackets": "^2.1.4",
1588− "extend-shallow": "^2.0.1",
1589− "fragment-cache": "^0.2.1",
1590− "regex-not": "^1.0.0",
1591− "snapdragon": "^0.8.1",
1592− "to-regex": "^3.0.1"
1593− },
1594− "dependencies": {
1595− "define-property": {
1596− "version": "1.0.0",
1597− "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
1598− "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
1599− "dev": true,
1600− "requires": {
1601− "is-descriptor": "^1.0.0"
1602− }
1603− },
1604− "extend-shallow": {
1605− "version": "2.0.1",
1606− "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
1607− "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
1608− "dev": true,
1609− "requires": {
1610− "is-extendable": "^0.1.0"
1611− }
1612− },
1613− "is-accessor-descriptor": {
1614− "version": "1.0.0",
1615− "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
1616− "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
1617− "dev": true,
1618− "requires": {
1619− "kind-of": "^6.0.0"
1620− }
1621− },
1622− "is-data-descriptor": {
1623− "version": "1.0.0",
1624− "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
1625− "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
1626− "dev": true,
1627− "requires": {
1628− "kind-of": "^6.0.0"
1629− }
1630− },
1631− "is-descriptor": {
1632− "version": "1.0.2",
1633− "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
1634− "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
1635− "dev": true,
1636− "requires": {
1637− "is-accessor-descriptor": "^1.0.0",
1638− "is-data-descriptor": "^1.0.0",
1639− "kind-of": "^6.0.2"
1640− }
1641− }
1642− }
1643− },
1644− "fast-deep-equal": {
1645− "version": "2.0.1",
1646− "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
1647− "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
1648− "dev": true
1649− },
1650− "fast-json-stable-stringify": {
1651− "version": "2.0.0",
1652− "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
1653− "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
1654− "dev": true
1655− },
1656− "faye-websocket": {
1657− "version": "0.10.0",
1658− "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
1659− "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
1660− "dev": true,
1661− "requires": {
1662− "websocket-driver": ">=0.5.1"
1663− }
1664− },
1665− "fill-range": {
1666− "version": "4.0.0",
1667− "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
1668− "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
1669− "dev": true,
1670− "requires": {
1671− "extend-shallow": "^2.0.1",
1672− "is-number": "^3.0.0",
1673− "repeat-string": "^1.6.1",
1674− "to-regex-range": "^2.1.0"
1675− },
1676− "dependencies": {
1677− "extend-shallow": {
1678− "version": "2.0.1",
1679− "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
1680− "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
1681− "dev": true,
1682− "requires": {
1683− "is-extendable": "^0.1.0"
1684− }
1685− }
1686− }
1687− },
1688− "finalhandler": {
1689− "version": "1.1.1",
1690− "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
1691− "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
1692− "dev": true,
1693− "requires": {
1694− "debug": "2.6.9",
1695− "encodeurl": "~1.0.2",
1696− "escape-html": "~1.0.3",
1697− "on-finished": "~2.3.0",
1698− "parseurl": "~1.3.2",
1699− "statuses": "~1.4.0",
1700− "unpipe": "~1.0.0"
1701− },
1702− "dependencies": {
1703− "debug": {
1704− "version": "2.6.9",
1705− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
1706− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
1707− "dev": true,
1708− "requires": {
1709− "ms": "2.0.0"
1710− }
1711− }
1712− }
1713− },
1714− "find-cache-dir": {
1715− "version": "1.0.0",
1716− "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz",
1717− "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=",
1718− "dev": true,
1719− "requires": {
1720− "commondir": "^1.0.1",
1721− "make-dir": "^1.0.0",
1722− "pkg-dir": "^2.0.0"
1723− }
1724− },
1725− "find-up": {
1726− "version": "2.1.0",
1727− "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
1728− "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
1729− "dev": true,
1730− "requires": {
1731− "locate-path": "^2.0.0"
1732− }
1733− },
1734− "flush-write-stream": {
1735− "version": "1.0.3",
1736− "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
1737− "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
1738− "dev": true,
1739− "requires": {
1740− "inherits": "^2.0.1",
1741− "readable-stream": "^2.0.4"
1742− }
1743− },
1744− "follow-redirects": {
1745− "version": "1.5.9",
1746− "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz",
1747− "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==",
1748− "dev": true,
1749− "requires": {
1750− "debug": "=3.1.0"
1751− },
1752− "dependencies": {
1753− "debug": {
1754− "version": "3.1.0",
1755− "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
1756− "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
1757− "dev": true,
1758− "requires": {
1759− "ms": "2.0.0"
1760− }
1761− }
1762− }
1763− },
1764− "for-in": {
1765− "version": "1.0.2",
1766− "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
1767− "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
1768− "dev": true
1769− },
1770− "forwarded": {
1771− "version": "0.1.2",
1772− "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
1773− "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
1774− "dev": true
1775− },
1776− "fragment-cache": {
1777− "version": "0.2.1",
1778− "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
1779− "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
1780− "dev": true,
1781− "requires": {
1782− "map-cache": "^0.2.2"
1783− }
1784− },
1785− "fresh": {
1786− "version": "0.5.2",
1787− "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
1788− "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
1789− "dev": true
1790− },
1791− "from2": {
1792− "version": "2.3.0",
1793− "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
1794− "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
1795− "dev": true,
1796− "requires": {
1797− "inherits": "^2.0.1",
1798− "readable-stream": "^2.0.0"
1799− }
1800− },
1801− "fs-write-stream-atomic": {
1802− "version": "1.0.10",
1803− "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
1804− "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
1805− "dev": true,
1806− "requires": {
1807− "graceful-fs": "^4.1.2",
1808− "iferr": "^0.1.5",
1809− "imurmurhash": "^0.1.4",
1810− "readable-stream": "1 || 2"
1811− }
1812− },
1813− "fs.realpath": {
1814− "version": "1.0.0",
1815− "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
1816− "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
1817− "dev": true
1818− },
1819− "fsevents": {
1820− "version": "1.2.4",
1821− "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
1822− "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
1823− "dev": true,
1824− "optional": true,
1825− "requires": {
1826− "nan": "^2.9.2",
1827− "node-pre-gyp": "^0.10.0"
1828− },
1829− "dependencies": {
1830− "abbrev": {
1831− "version": "1.1.1",
1832− "bundled": true,
1833− "dev": true,
1834− "optional": true
1835− },
1836− "ansi-regex": {
1837− "version": "2.1.1",
1838− "bundled": true,
1839− "dev": true
1840− },
1841− "aproba": {
1842− "version": "1.2.0",
1843− "bundled": true,
1844− "dev": true,
1845− "optional": true
1846− },
1847− "are-we-there-yet": {
1848− "version": "1.1.4",
1849− "bundled": true,
1850− "dev": true,
1851− "optional": true,
1852− "requires": {
1853− "delegates": "^1.0.0",
1854− "readable-stream": "^2.0.6"
1855− }
1856− },
1857− "balanced-match": {
1858− "version": "1.0.0",
1859− "bundled": true,
1860− "dev": true
1861− },
1862− "brace-expansion": {
1863− "version": "1.1.11",
1864− "bundled": true,
1865− "dev": true,
1866− "requires": {
1867− "balanced-match": "^1.0.0",
1868− "concat-map": "0.0.1"
1869− }
1870− },
1871− "chownr": {
1872− "version": "1.0.1",
1873− "bundled": true,
1874− "dev": true,
1875− "optional": true
1876− },
1877− "code-point-at": {
1878− "version": "1.1.0",
1879− "bundled": true,
1880− "dev": true
1881− },
1882− "concat-map": {
1883− "version": "0.0.1",
1884− "bundled": true,
1885− "dev": true
1886− },
1887− "console-control-strings": {
1888− "version": "1.1.0",
1889− "bundled": true,
1890− "dev": true
1891− },
1892− "core-util-is": {
1893− "version": "1.0.2",
1894− "bundled": true,
1895− "dev": true,
1896− "optional": true
1897− },
1898− "debug": {
1899− "version": "2.6.9",
1900− "bundled": true,
1901− "dev": true,
1902− "optional": true,
1903− "requires": {
1904− "ms": "2.0.0"
1905− }
1906− },
1907− "deep-extend": {
1908− "version": "0.5.1",
1909− "bundled": true,
1910− "dev": true,
1911− "optional": true
1912− },
1913− "delegates": {
1914− "version": "1.0.0",
1915− "bundled": true,
1916− "dev": true,
1917− "optional": true
1918− },
1919− "detect-libc": {
1920− "version": "1.0.3",
1921− "bundled": true,
1922− "dev": true,
1923− "optional": true
1924− },
1925− "fs-minipass": {
1926− "version": "1.2.5",
1927− "bundled": true,
1928− "dev": true,
1929− "optional": true,
1930− "requires": {
1931− "minipass": "^2.2.1"
1932− }
1933− },
1934− "fs.realpath": {
1935− "version": "1.0.0",
1936− "bundled": true,
1937− "dev": true,
1938− "optional": true
1939− },
1940− "gauge": {
1941− "version": "2.7.4",
1942− "bundled": true,
1943− "dev": true,
1944− "optional": true,
1945− "requires": {
1946− "aproba": "^1.0.3",
1947− "console-control-strings": "^1.0.0",
1948− "has-unicode": "^2.0.0",
1949− "object-assign": "^4.1.0",
1950− "signal-exit": "^3.0.0",
1951− "string-width": "^1.0.1",
1952− "strip-ansi": "^3.0.1",
1953− "wide-align": "^1.1.0"
1954− }
1955− },
1956− "glob": {
1957− "version": "7.1.2",
1958− "bundled": true,
1959− "dev": true,
1960− "optional": true,
1961− "requires": {
1962− "fs.realpath": "^1.0.0",
1963− "inflight": "^1.0.4",
1964− "inherits": "2",
1965− "minimatch": "^3.0.4",
1966− "once": "^1.3.0",
1967− "path-is-absolute": "^1.0.0"
1968− }
1969− },
1970− "has-unicode": {
1971− "version": "2.0.1",
1972− "bundled": true,
1973− "dev": true,
1974− "optional": true
1975− },
1976− "iconv-lite": {
1977− "version": "0.4.21",
1978− "bundled": true,
1979− "dev": true,
1980− "optional": true,
1981− "requires": {
1982− "safer-buffer": "^2.1.0"
1983− }
1984− },
1985− "ignore-walk": {
1986− "version": "3.0.1",
1987− "bundled": true,
1988− "dev": true,
1989− "optional": true,
1990− "requires": {
1991− "minimatch": "^3.0.4"
1992− }
1993− },
1994− "inflight": {
1995− "version": "1.0.6",
1996− "bundled": true,
1997− "dev": true,
1998− "optional": true,
1999− "requires": {
2000− "once": "^1.3.0",
2001− "wrappy": "1"
2002− }
2003− },
2004− "inherits": {
2005− "version": "2.0.3",
2006− "bundled": true,
2007− "dev": true
2008− },
2009− "ini": {
2010− "version": "1.3.5",
2011− "bundled": true,
2012− "dev": true,
2013− "optional": true
2014− },
2015− "is-fullwidth-code-point": {
2016− "version": "1.0.0",
2017− "bundled": true,
2018− "dev": true,
2019− "requires": {
2020− "number-is-nan": "^1.0.0"
2021− }
2022− },
2023− "isarray": {
2024− "version": "1.0.0",
2025− "bundled": true,
2026− "dev": true,
2027− "optional": true
2028− },
2029− "minimatch": {
2030− "version": "3.0.4",
2031− "bundled": true,
2032− "dev": true,
2033− "requires": {
2034− "brace-expansion": "^1.1.7"
2035− }
2036− },
2037− "minimist": {
2038− "version": "0.0.8",
2039− "bundled": true,
2040− "dev": true
2041− },
2042− "minipass": {
2043− "version": "2.2.4",
2044− "bundled": true,
2045− "dev": true,
2046− "requires": {
2047− "safe-buffer": "^5.1.1",
2048− "yallist": "^3.0.0"
2049− }
2050− },
2051− "minizlib": {
2052− "version": "1.1.0",
2053− "bundled": true,
2054− "dev": true,
2055− "optional": true,
2056− "requires": {
2057− "minipass": "^2.2.1"
2058− }
2059− },
2060− "mkdirp": {
2061− "version": "0.5.1",
2062− "bundled": true,
2063− "dev": true,
2064− "requires": {
2065− "minimist": "0.0.8"
2066− }
2067− },
2068− "ms": {
2069− "version": "2.0.0",
2070− "bundled": true,
2071− "dev": true,
2072− "optional": true
2073− },
2074− "needle": {
2075− "version": "2.2.0",
2076− "bundled": true,
2077− "dev": true,
2078− "optional": true,
2079− "requires": {
2080− "debug": "^2.1.2",
2081− "iconv-lite": "^0.4.4",
2082− "sax": "^1.2.4"
2083− }
2084− },
2085− "node-pre-gyp": {
2086− "version": "0.10.0",
2087− "bundled": true,
2088− "dev": true,
2089− "optional": true,
2090− "requires": {
2091− "detect-libc": "^1.0.2",
2092− "mkdirp": "^0.5.1",
2093− "needle": "^2.2.0",
2094− "nopt": "^4.0.1",
2095− "npm-packlist": "^1.1.6",
2096− "npmlog": "^4.0.2",
2097− "rc": "^1.1.7",
2098− "rimraf": "^2.6.1",
2099− "semver": "^5.3.0",
2100− "tar": "^4"
2101− }
2102− },
2103− "nopt": {
2104− "version": "4.0.1",
2105− "bundled": true,
2106− "dev": true,
2107− "optional": true,
2108− "requires": {
2109− "abbrev": "1",
2110− "osenv": "^0.1.4"
2111− }
2112− },
2113− "npm-bundled": {
2114− "version": "1.0.3",
2115− "bundled": true,
2116− "dev": true,
2117− "optional": true
2118− },
2119− "npm-packlist": {
2120− "version": "1.1.10",
2121− "bundled": true,
2122− "dev": true,
2123− "optional": true,
2124− "requires": {
2125− "ignore-walk": "^3.0.1",
2126− "npm-bundled": "^1.0.1"
2127− }
2128− },
2129− "npmlog": {
2130− "version": "4.1.2",
2131− "bundled": true,
2132− "dev": true,
2133− "optional": true,
2134− "requires": {
2135− "are-we-there-yet": "~1.1.2",
2136− "console-control-strings": "~1.1.0",
2137− "gauge": "~2.7.3",
2138− "set-blocking": "~2.0.0"
2139− }
2140− },
2141− "number-is-nan": {
2142− "version": "1.0.1",
2143− "bundled": true,
2144− "dev": true
2145− },
2146− "object-assign": {
2147− "version": "4.1.1",
2148− "bundled": true,
2149− "dev": true,
2150− "optional": true
2151− },
2152− "once": {
2153− "version": "1.4.0",
2154− "bundled": true,
2155− "dev": true,
2156− "requires": {
2157− "wrappy": "1"
2158− }
2159− },
2160− "os-homedir": {
2161− "version": "1.0.2",
2162− "bundled": true,
2163− "dev": true,
2164− "optional": true
2165− },
2166− "os-tmpdir": {
2167− "version": "1.0.2",
2168− "bundled": true,
2169− "dev": true,
2170− "optional": true
2171− },
2172− "osenv": {
2173− "version": "0.1.5",
2174− "bundled": true,
2175− "dev": true,
2176− "optional": true,
2177− "requires": {
2178− "os-homedir": "^1.0.0",
2179− "os-tmpdir": "^1.0.0"
2180− }
2181− },
2182− "path-is-absolute": {
2183− "version": "1.0.1",
2184− "bundled": true,
2185− "dev": true,
2186− "optional": true
2187− },
2188− "process-nextick-args": {
2189− "version": "2.0.0",
2190− "bundled": true,
2191− "dev": true,
2192− "optional": true
2193− },
2194− "rc": {
2195− "version": "1.2.7",
2196− "bundled": true,
2197− "dev": true,
2198− "optional": true,
2199− "requires": {
2200− "deep-extend": "^0.5.1",
2201− "ini": "~1.3.0",
2202− "minimist": "^1.2.0",
2203− "strip-json-comments": "~2.0.1"
2204− },
2205− "dependencies": {
2206− "minimist": {
2207− "version": "1.2.0",
2208− "bundled": true,
2209− "dev": true,
2210− "optional": true
2211− }
2212− }
2213− },
2214− "readable-stream": {
2215− "version": "2.3.6",
2216− "bundled": true,
2217− "dev": true,
2218− "optional": true,
2219− "requires": {
2220− "core-util-is": "~1.0.0",
2221− "inherits": "~2.0.3",
2222− "isarray": "~1.0.0",
2223− "process-nextick-args": "~2.0.0",
2224− "safe-buffer": "~5.1.1",
2225− "string_decoder": "~1.1.1",
2226− "util-deprecate": "~1.0.1"
2227− }
2228− },
2229− "rimraf": {
2230− "version": "2.6.2",
2231− "bundled": true,
2232− "dev": true,
2233− "optional": true,
2234− "requires": {
2235− "glob": "^7.0.5"
2236− }
2237− },
2238− "safe-buffer": {
2239− "version": "5.1.1",
2240− "bundled": true,
2241− "dev": true
2242− },
2243− "safer-buffer": {
2244− "version": "2.1.2",
2245− "bundled": true,
2246− "dev": true,
2247− "optional": true
2248− },
2249− "sax": {
2250− "version": "1.2.4",
2251− "bundled": true,
2252− "dev": true,
2253− "optional": true
2254− },
2255− "semver": {
2256− "version": "5.5.0",
2257− "bundled": true,
2258− "dev": true,
2259− "optional": true
2260− },
2261− "set-blocking": {
2262− "version": "2.0.0",
2263− "bundled": true,
2264− "dev": true,
2265− "optional": true
2266− },
2267− "signal-exit": {
2268− "version": "3.0.2",
2269− "bundled": true,
2270− "dev": true,
2271− "optional": true
2272− },
2273− "string-width": {
2274− "version": "1.0.2",
2275− "bundled": true,
2276− "dev": true,
2277− "requires": {
2278− "code-point-at": "^1.0.0",
2279− "is-fullwidth-code-point": "^1.0.0",
2280− "strip-ansi": "^3.0.0"
2281− }
2282− },
2283− "string_decoder": {
2284− "version": "1.1.1",
2285− "bundled": true,
2286− "dev": true,
2287− "optional": true,
2288− "requires": {
2289− "safe-buffer": "~5.1.0"
2290− }
2291− },
2292− "strip-ansi": {
2293− "version": "3.0.1",
2294− "bundled": true,
2295− "dev": true,
2296− "requires": {
2297− "ansi-regex": "^2.0.0"
2298− }
2299− },
2300− "strip-json-comments": {
2301− "version": "2.0.1",
2302− "bundled": true,
2303− "dev": true,
2304− "optional": true
2305− },
2306− "tar": {
2307− "version": "4.4.1",
2308− "bundled": true,
2309− "dev": true,
2310− "optional": true,
2311− "requires": {
2312− "chownr": "^1.0.1",
2313− "fs-minipass": "^1.2.5",
2314− "minipass": "^2.2.4",
2315− "minizlib": "^1.1.0",
2316− "mkdirp": "^0.5.0",
2317− "safe-buffer": "^5.1.1",
2318− "yallist": "^3.0.2"
2319− }
2320− },
2321− "util-deprecate": {
2322− "version": "1.0.2",
2323− "bundled": true,
2324− "dev": true,
2325− "optional": true
2326− },
2327− "wide-align": {
2328− "version": "1.1.2",
2329− "bundled": true,
2330− "dev": true,
2331− "optional": true,
2332− "requires": {
2333− "string-width": "^1.0.2"
2334− }
2335− },
2336− "wrappy": {
2337− "version": "1.0.2",
2338− "bundled": true,
2339− "dev": true
2340− },
2341− "yallist": {
2342− "version": "3.0.2",
2343− "bundled": true,
2344− "dev": true
2345− }
2346− }
2347− },
2348− "get-caller-file": {
2349− "version": "1.0.3",
2350− "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
2351− "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
2352− "dev": true
2353− },
2354− "get-stream": {
2355− "version": "3.0.0",
2356− "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
2357− "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
2358− "dev": true
2359− },
2360− "get-value": {
2361− "version": "2.0.6",
2362− "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
2363− "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
2364− "dev": true
2365− },
2366− "glob": {
2367− "version": "7.1.2",
2368− "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
2369− "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
2370− "dev": true,
2371− "requires": {
2372− "fs.realpath": "^1.0.0",
2373− "inflight": "^1.0.4",
2374− "inherits": "2",
2375− "minimatch": "^3.0.4",
2376− "once": "^1.3.0",
2377− "path-is-absolute": "^1.0.0"
2378− }
2379− },
2380− "glob-parent": {
2381− "version": "3.1.0",
2382− "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
2383− "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
2384− "dev": true,
2385− "requires": {
2386− "is-glob": "^3.1.0",
2387− "path-dirname": "^1.0.0"
2388− },
2389− "dependencies": {
2390− "is-glob": {
2391− "version": "3.1.0",
2392− "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
2393− "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
2394− "dev": true,
2395− "requires": {
2396− "is-extglob": "^2.1.0"
2397− }
2398− }
2399− }
2400− },
2401− "global-modules-path": {
2402− "version": "2.3.0",
2403− "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.0.tgz",
2404− "integrity": "sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag==",
2405− "dev": true
2406− },
2407− "globby": {
2408− "version": "6.1.0",
2409− "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
2410− "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
2411− "dev": true,
2412− "requires": {
2413− "array-union": "^1.0.1",
2414− "glob": "^7.0.3",
2415− "object-assign": "^4.0.1",
2416− "pify": "^2.0.0",
2417− "pinkie-promise": "^2.0.0"
2418− },
2419− "dependencies": {
2420− "pify": {
2421− "version": "2.3.0",
2422− "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
2423− "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
2424− "dev": true
2425− }
2426− }
2427− },
2428− "graceful-fs": {
2429− "version": "4.1.11",
2430− "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
2431− "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
2432− "dev": true
2433− },
2434− "handle-thing": {
2435− "version": "1.2.5",
2436− "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
2437− "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=",
2438− "dev": true
2439− },
2440− "has-flag": {
2441− "version": "3.0.0",
2442− "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
2443− "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
2444− "dev": true
2445− },
2446− "has-value": {
2447− "version": "1.0.0",
2448− "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
2449− "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
2450− "dev": true,
2451− "requires": {
2452− "get-value": "^2.0.6",
2453− "has-values": "^1.0.0",
2454− "isobject": "^3.0.0"
2455− }
2456− },
2457− "has-values": {
2458− "version": "1.0.0",
2459− "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
2460− "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
2461− "dev": true,
2462− "requires": {
2463− "is-number": "^3.0.0",
2464− "kind-of": "^4.0.0"
2465− },
2466− "dependencies": {
2467− "kind-of": {
2468− "version": "4.0.0",
2469− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
2470− "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
2471− "dev": true,
2472− "requires": {
2473− "is-buffer": "^1.1.5"
2474− }
2475− }
2476− }
2477− },
2478− "hash-base": {
2479− "version": "3.0.4",
2480− "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
2481− "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
2482− "dev": true,
2483− "requires": {
2484− "inherits": "^2.0.1",
2485− "safe-buffer": "^5.0.1"
2486− }
2487− },
2488− "hash.js": {
2489− "version": "1.1.5",
2490− "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz",
2491− "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==",
2492− "dev": true,
2493− "requires": {
2494− "inherits": "^2.0.3",
2495− "minimalistic-assert": "^1.0.1"
2496− }
2497− },
2498− "hmac-drbg": {
2499− "version": "1.0.1",
2500− "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
2501− "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
2502− "dev": true,
2503− "requires": {
2504− "hash.js": "^1.0.3",
2505− "minimalistic-assert": "^1.0.0",
2506− "minimalistic-crypto-utils": "^1.0.1"
2507− }
2508− },
2509− "hpack.js": {
2510− "version": "2.1.6",
2511− "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
2512− "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
2513− "dev": true,
2514− "requires": {
2515− "inherits": "^2.0.1",
2516− "obuf": "^1.0.0",
2517− "readable-stream": "^2.0.1",
2518− "wbuf": "^1.1.0"
2519− }
2520− },
2521− "html-entities": {
2522− "version": "1.2.1",
2523− "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
2524− "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=",
2525− "dev": true
2526− },
2527− "http-deceiver": {
2528− "version": "1.2.7",
2529− "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
2530− "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
2531− "dev": true
2532− },
2533− "http-errors": {
2534− "version": "1.6.3",
2535− "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
2536− "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
2537− "dev": true,
2538− "requires": {
2539− "depd": "~1.1.2",
2540− "inherits": "2.0.3",
2541− "setprototypeof": "1.1.0",
2542− "statuses": ">= 1.4.0 < 2"
2543− }
2544− },
2545− "http-parser-js": {
2546− "version": "0.5.0",
2547− "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz",
2548− "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==",
2549− "dev": true
2550− },
2551− "http-proxy": {
2552− "version": "1.17.0",
2553− "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
2554− "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
2555− "dev": true,
2556− "requires": {
2557− "eventemitter3": "^3.0.0",
2558− "follow-redirects": "^1.0.0",
2559− "requires-port": "^1.0.0"
2560− }
2561− },
2562− "http-proxy-middleware": {
2563− "version": "0.18.0",
2564− "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
2565− "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==",
2566− "dev": true,
2567− "requires": {
2568− "http-proxy": "^1.16.2",
2569− "is-glob": "^4.0.0",
2570− "lodash": "^4.17.5",
2571− "micromatch": "^3.1.9"
2572− }
2573− },
2574− "https-browserify": {
2575− "version": "1.0.0",
2576− "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
2577− "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
2578− "dev": true
2579− },
2580− "iconv-lite": {
2581− "version": "0.4.23",
2582− "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
2583− "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
2584− "dev": true,
2585− "requires": {
2586− "safer-buffer": ">= 2.1.2 < 3"
2587− }
2588− },
2589− "ieee754": {
2590− "version": "1.1.12",
2591− "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
2592− "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==",
2593− "dev": true
2594− },
2595− "iferr": {
2596− "version": "0.1.5",
2597− "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
2598− "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
2599− "dev": true
2600− },
2601− "ignore": {
2602− "version": "3.3.10",
2603− "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
2604− "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
2605− "dev": true
2606− },
2607− "import-local": {
2608− "version": "2.0.0",
2609− "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
2610− "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
2611− "dev": true,
2612− "requires": {
2613− "pkg-dir": "^3.0.0",
2614− "resolve-cwd": "^2.0.0"
2615− },
2616− "dependencies": {
2617− "find-up": {
2618− "version": "3.0.0",
2619− "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
2620− "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
2621− "dev": true,
2622− "requires": {
2623− "locate-path": "^3.0.0"
2624− }
2625− },
2626− "locate-path": {
2627− "version": "3.0.0",
2628− "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
2629− "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
2630− "dev": true,
2631− "requires": {
2632− "p-locate": "^3.0.0",
2633− "path-exists": "^3.0.0"
2634− }
2635− },
2636− "p-limit": {
2637− "version": "2.0.0",
2638− "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz",
2639− "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==",
2640− "dev": true,
2641− "requires": {
2642− "p-try": "^2.0.0"
2643− }
2644− },
2645− "p-locate": {
2646− "version": "3.0.0",
2647− "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
2648− "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
2649− "dev": true,
2650− "requires": {
2651− "p-limit": "^2.0.0"
2652− }
2653− },
2654− "p-try": {
2655− "version": "2.0.0",
2656− "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
2657− "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
2658− "dev": true
2659− },
2660− "pkg-dir": {
2661− "version": "3.0.0",
2662− "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
2663− "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
2664− "dev": true,
2665− "requires": {
2666− "find-up": "^3.0.0"
2667− }
2668− }
2669− }
2670− },
2671− "imurmurhash": {
2672− "version": "0.1.4",
2673− "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
2674− "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
2675− "dev": true
2676− },
2677− "indexof": {
2678− "version": "0.0.1",
2679− "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
2680− "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
2681− "dev": true
2682− },
2683− "inflight": {
2684− "version": "1.0.6",
2685− "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
2686− "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
2687− "dev": true,
2688− "requires": {
2689− "once": "^1.3.0",
2690− "wrappy": "1"
2691− }
2692− },
2693− "inherits": {
2694− "version": "2.0.3",
2695− "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
2696− "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
2697− "dev": true
2698− },
2699− "internal-ip": {
2700− "version": "3.0.1",
2701− "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz",
2702− "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==",
2703− "dev": true,
2704− "requires": {
2705− "default-gateway": "^2.6.0",
2706− "ipaddr.js": "^1.5.2"
2707− }
2708− },
2709− "interpret": {
2710− "version": "1.1.0",
2711− "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
2712− "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=",
2713− "dev": true
2714− },
2715− "invert-kv": {
2716− "version": "2.0.0",
2717− "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
2718− "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
2719− "dev": true
2720− },
2721− "ip": {
2722− "version": "1.1.5",
2723− "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
2724− "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
2725− "dev": true
2726− },
2727− "ip-regex": {
2728− "version": "2.1.0",
2729− "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
2730− "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
2731− "dev": true
2732− },
2733− "ipaddr.js": {
2734− "version": "1.8.0",
2735− "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
2736− "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=",
2737− "dev": true
2738− },
2739− "is-accessor-descriptor": {
2740− "version": "0.1.6",
2741− "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
2742− "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
2743− "dev": true,
2744− "requires": {
2745− "kind-of": "^3.0.2"
2746− },
2747− "dependencies": {
2748− "kind-of": {
2749− "version": "3.2.2",
2750− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
2751− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
2752− "dev": true,
2753− "requires": {
2754− "is-buffer": "^1.1.5"
2755− }
2756− }
2757− }
2758− },
2759− "is-binary-path": {
2760− "version": "1.0.1",
2761− "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
2762− "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
2763− "dev": true,
2764− "requires": {
2765− "binary-extensions": "^1.0.0"
2766− }
2767− },
2768− "is-buffer": {
2769− "version": "1.1.6",
2770− "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
2771− "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
2772− "dev": true
2773− },
2774− "is-data-descriptor": {
2775− "version": "0.1.4",
2776− "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
2777− "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
2778− "dev": true,
2779− "requires": {
2780− "kind-of": "^3.0.2"
2781− },
2782− "dependencies": {
2783− "kind-of": {
2784− "version": "3.2.2",
2785− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
2786− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
2787− "dev": true,
2788− "requires": {
2789− "is-buffer": "^1.1.5"
2790− }
2791− }
2792− }
2793− },
2794− "is-descriptor": {
2795− "version": "0.1.6",
2796− "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
2797− "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
2798− "dev": true,
2799− "requires": {
2800− "is-accessor-descriptor": "^0.1.6",
2801− "is-data-descriptor": "^0.1.4",
2802− "kind-of": "^5.0.0"
2803− },
2804− "dependencies": {
2805− "kind-of": {
2806− "version": "5.1.0",
2807− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
2808− "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
2809− "dev": true
2810− }
2811− }
2812− },
2813− "is-extendable": {
2814− "version": "0.1.1",
2815− "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
2816− "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
2817− "dev": true
2818− },
2819− "is-extglob": {
2820− "version": "2.1.1",
2821− "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
2822− "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
2823− "dev": true
2824− },
2825− "is-fullwidth-code-point": {
2826− "version": "2.0.0",
2827− "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
2828− "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
2829− "dev": true
2830− },
2831− "is-glob": {
2832− "version": "4.0.0",
2833− "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
2834− "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
2835− "dev": true,
2836− "requires": {
2837− "is-extglob": "^2.1.1"
2838− }
2839− },
2840− "is-number": {
2841− "version": "3.0.0",
2842− "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
2843− "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
2844− "dev": true,
2845− "requires": {
2846− "kind-of": "^3.0.2"
2847− },
2848− "dependencies": {
2849− "kind-of": {
2850− "version": "3.2.2",
2851− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
2852− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
2853− "dev": true,
2854− "requires": {
2855− "is-buffer": "^1.1.5"
2856− }
2857− }
2858− }
2859− },
2860− "is-path-cwd": {
2861− "version": "1.0.0",
2862− "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
2863− "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
2864− "dev": true
2865− },
2866− "is-path-in-cwd": {
2867− "version": "1.0.1",
2868− "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
2869− "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
2870− "dev": true,
2871− "requires": {
2872− "is-path-inside": "^1.0.0"
2873− }
2874− },
2875− "is-path-inside": {
2876− "version": "1.0.1",
2877− "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
2878− "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
2879− "dev": true,
2880− "requires": {
2881− "path-is-inside": "^1.0.1"
2882− }
2883− },
2884− "is-plain-object": {
2885− "version": "2.0.4",
2886− "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
2887− "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
2888− "dev": true,
2889− "requires": {
2890− "isobject": "^3.0.1"
2891− }
2892− },
2893− "is-stream": {
2894− "version": "1.1.0",
2895− "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
2896− "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
2897− "dev": true
2898− },
2899− "is-windows": {
2900− "version": "1.0.2",
2901− "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
2902− "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
2903− "dev": true
2904− },
2905− "is-wsl": {
2906− "version": "1.1.0",
2907− "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
2908− "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
2909− "dev": true
2910− },
2911− "isarray": {
2912− "version": "1.0.0",
2913− "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
2914− "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
2915− "dev": true
2916− },
2917− "isexe": {
2918− "version": "2.0.0",
2919− "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
2920− "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
2921− "dev": true
2922− },
2923− "isobject": {
2924− "version": "3.0.1",
2925− "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
2926− "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
2927− "dev": true
2928− },
2929− "json-parse-better-errors": {
2930− "version": "1.0.2",
2931− "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
2932− "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
2933− "dev": true
2934− },
2935− "json-schema-traverse": {
2936− "version": "0.4.1",
2937− "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
2938− "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
2939− "dev": true
2940− },
2941− "json3": {
2942− "version": "3.3.2",
2943− "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
2944− "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
2945− "dev": true
2946− },
2947− "json5": {
2948− "version": "0.5.1",
2949− "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
2950− "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
2951− "dev": true
2952− },
2953− "killable": {
2954− "version": "1.0.1",
2955− "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
2956− "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==",
2957− "dev": true
2958− },
2959− "kind-of": {
2960− "version": "6.0.2",
2961− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
2962− "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
2963− "dev": true
2964− },
2965− "lcid": {
2966− "version": "2.0.0",
2967− "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
2968− "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
2969− "dev": true,
2970− "requires": {
2971− "invert-kv": "^2.0.0"
2972− }
2973− },
2974− "loader-runner": {
2975− "version": "2.3.1",
2976− "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz",
2977− "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==",
2978− "dev": true
2979− },
2980− "loader-utils": {
2981− "version": "1.1.0",
2982− "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
2983− "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=",
2984− "dev": true,
2985− "requires": {
2986− "big.js": "^3.1.3",
2987− "emojis-list": "^2.0.0",
2988− "json5": "^0.5.0"
2989− }
2990− },
2991− "locate-path": {
2992− "version": "2.0.0",
2993− "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
2994− "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
2995− "dev": true,
2996− "requires": {
2997− "p-locate": "^2.0.0",
2998− "path-exists": "^3.0.0"
2999− }
3000− },
3001− "lodash": {
3002− "version": "4.17.11",
3003− "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
3004− "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
3005− "dev": true
3006− },
3007− "lodash.debounce": {
3008− "version": "4.0.8",
3009− "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
3010− "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
3011− "dev": true
3012− },
3013− "loglevel": {
3014− "version": "1.6.1",
3015− "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
3016− "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=",
3017− "dev": true
3018− },
3019− "lru-cache": {
3020− "version": "4.1.3",
3021− "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
3022− "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==",
3023− "dev": true,
3024− "requires": {
3025− "pseudomap": "^1.0.2",
3026− "yallist": "^2.1.2"
3027− }
3028− },
3029− "make-dir": {
3030− "version": "1.3.0",
3031− "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
3032− "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
3033− "dev": true,
3034− "requires": {
3035− "pify": "^3.0.0"
3036− }
3037− },
3038− "map-age-cleaner": {
3039− "version": "0.1.2",
3040− "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz",
3041− "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==",
3042− "dev": true,
3043− "requires": {
3044− "p-defer": "^1.0.0"
3045− }
3046− },
3047− "map-cache": {
3048− "version": "0.2.2",
3049− "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
3050− "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
3051− "dev": true
3052− },
3053− "map-visit": {
3054− "version": "1.0.0",
3055− "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
3056− "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
3057− "dev": true,
3058− "requires": {
3059− "object-visit": "^1.0.0"
3060− }
3061− },
3062− "md5.js": {
3063− "version": "1.3.5",
3064− "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
3065− "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
3066− "dev": true,
3067− "requires": {
3068− "hash-base": "^3.0.0",
3069− "inherits": "^2.0.1",
3070− "safe-buffer": "^5.1.2"
3071− }
3072− },
3073− "media-typer": {
3074− "version": "0.3.0",
3075− "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
3076− "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
3077− "dev": true
3078− },
3079− "mem": {
3080− "version": "4.0.0",
3081− "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz",
3082− "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==",
3083− "dev": true,
3084− "requires": {
3085− "map-age-cleaner": "^0.1.1",
3086− "mimic-fn": "^1.0.0",
3087− "p-is-promise": "^1.1.0"
3088− }
3089− },
3090− "memory-fs": {
3091− "version": "0.4.1",
3092− "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
3093− "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
3094− "dev": true,
3095− "requires": {
3096− "errno": "^0.1.3",
3097− "readable-stream": "^2.0.1"
3098− }
3099− },
3100− "merge-descriptors": {
3101− "version": "1.0.1",
3102− "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
3103− "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
3104− "dev": true
3105− },
3106− "methods": {
3107− "version": "1.1.2",
3108− "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
3109− "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
3110− "dev": true
3111− },
3112− "micromatch": {
3113− "version": "3.1.10",
3114− "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
3115− "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
3116− "dev": true,
3117− "requires": {
3118− "arr-diff": "^4.0.0",
3119− "array-unique": "^0.3.2",
3120− "braces": "^2.3.1",
3121− "define-property": "^2.0.2",
3122− "extend-shallow": "^3.0.2",
3123− "extglob": "^2.0.4",
3124− "fragment-cache": "^0.2.1",
3125− "kind-of": "^6.0.2",
3126− "nanomatch": "^1.2.9",
3127− "object.pick": "^1.3.0",
3128− "regex-not": "^1.0.0",
3129− "snapdragon": "^0.8.1",
3130− "to-regex": "^3.0.2"
3131− }
3132− },
3133− "miller-rabin": {
3134− "version": "4.0.1",
3135− "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
3136− "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
3137− "dev": true,
3138− "requires": {
3139− "bn.js": "^4.0.0",
3140− "brorand": "^1.0.1"
3141− }
3142− },
3143− "mime": {
3144− "version": "1.4.1",
3145− "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
3146− "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
3147− "dev": true
3148− },
3149− "mime-db": {
3150− "version": "1.37.0",
3151− "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
3152− "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==",
3153− "dev": true
3154− },
3155− "mime-types": {
3156− "version": "2.1.21",
3157− "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
3158− "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
3159− "dev": true,
3160− "requires": {
3161− "mime-db": "~1.37.0"
3162− }
3163− },
3164− "mimic-fn": {
3165− "version": "1.2.0",
3166− "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
3167− "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
3168− "dev": true
3169− },
3170− "minimalistic-assert": {
3171− "version": "1.0.1",
3172− "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
3173− "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
3174− "dev": true
3175− },
3176− "minimalistic-crypto-utils": {
3177− "version": "1.0.1",
3178− "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
3179− "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
3180− "dev": true
3181− },
3182− "minimatch": {
3183− "version": "3.0.4",
3184− "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
3185− "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
3186− "dev": true,
3187− "requires": {
3188− "brace-expansion": "^1.1.7"
3189− }
3190− },
3191− "minimist": {
3192− "version": "0.0.8",
3193− "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
3194− "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
3195− "dev": true
3196− },
3197− "mississippi": {
3198− "version": "2.0.0",
3199− "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz",
3200− "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==",
3201− "dev": true,
3202− "requires": {
3203− "concat-stream": "^1.5.0",
3204− "duplexify": "^3.4.2",
3205− "end-of-stream": "^1.1.0",
3206− "flush-write-stream": "^1.0.0",
3207− "from2": "^2.1.0",
3208− "parallel-transform": "^1.1.0",
3209− "pump": "^2.0.1",
3210− "pumpify": "^1.3.3",
3211− "stream-each": "^1.1.0",
3212− "through2": "^2.0.0"
3213− }
3214− },
3215− "mixin-deep": {
3216− "version": "1.3.1",
3217− "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
3218− "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
3219− "dev": true,
3220− "requires": {
3221− "for-in": "^1.0.2",
3222− "is-extendable": "^1.0.1"
3223− },
3224− "dependencies": {
3225− "is-extendable": {
3226− "version": "1.0.1",
3227− "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
3228− "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
3229− "dev": true,
3230− "requires": {
3231− "is-plain-object": "^2.0.4"
3232− }
3233− }
3234− }
3235− },
3236− "mkdirp": {
3237− "version": "0.5.1",
3238− "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
3239− "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
3240− "dev": true,
3241− "requires": {
3242− "minimist": "0.0.8"
3243− }
3244− },
3245− "move-concurrently": {
3246− "version": "1.0.1",
3247− "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
3248− "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
3249− "dev": true,
3250− "requires": {
3251− "aproba": "^1.1.1",
3252− "copy-concurrently": "^1.0.0",
3253− "fs-write-stream-atomic": "^1.0.8",
3254− "mkdirp": "^0.5.1",
3255− "rimraf": "^2.5.4",
3256− "run-queue": "^1.0.3"
3257− }
3258− },
3259− "ms": {
3260− "version": "2.0.0",
3261− "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
3262− "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
3263− "dev": true
3264− },
3265− "multicast-dns": {
3266− "version": "6.2.3",
3267− "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
3268− "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
3269− "dev": true,
3270− "requires": {
3271− "dns-packet": "^1.3.1",
3272− "thunky": "^1.0.2"
3273− }
3274− },
3275− "multicast-dns-service-types": {
3276− "version": "1.1.0",
3277− "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
3278− "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
3279− "dev": true
3280− },
3281− "nan": {
3282− "version": "2.10.0",
3283− "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
3284− "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
3285− "dev": true,
3286− "optional": true
3287− },
3288− "nanomatch": {
3289− "version": "1.2.13",
3290− "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
3291− "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
3292− "dev": true,
3293− "requires": {
3294− "arr-diff": "^4.0.0",
3295− "array-unique": "^0.3.2",
3296− "define-property": "^2.0.2",
3297− "extend-shallow": "^3.0.2",
3298− "fragment-cache": "^0.2.1",
3299− "is-windows": "^1.0.2",
3300− "kind-of": "^6.0.2",
3301− "object.pick": "^1.3.0",
3302− "regex-not": "^1.0.0",
3303− "snapdragon": "^0.8.1",
3304− "to-regex": "^3.0.1"
3305− }
3306− },
3307− "negotiator": {
3308− "version": "0.6.1",
3309− "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
3310− "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
3311− "dev": true
3312− },
3313− "neo-async": {
3314− "version": "2.6.0",
3315− "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz",
3316− "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
3317− "dev": true
3318− },
3319− "nice-try": {
3320− "version": "1.0.4",
3321− "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz",
3322− "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==",
3323− "dev": true
3324− },
3325− "node-forge": {
3326− "version": "0.7.5",
3327− "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz",
3328− "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==",
3329− "dev": true
3330− },
3331− "node-libs-browser": {
3332− "version": "2.1.0",
3333− "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz",
3334− "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==",
3335− "dev": true,
3336− "requires": {
3337− "assert": "^1.1.1",
3338− "browserify-zlib": "^0.2.0",
3339− "buffer": "^4.3.0",
3340− "console-browserify": "^1.1.0",
3341− "constants-browserify": "^1.0.0",
3342− "crypto-browserify": "^3.11.0",
3343− "domain-browser": "^1.1.1",
3344− "events": "^1.0.0",
3345− "https-browserify": "^1.0.0",
3346− "os-browserify": "^0.3.0",
3347− "path-browserify": "0.0.0",
3348− "process": "^0.11.10",
3349− "punycode": "^1.2.4",
3350− "querystring-es3": "^0.2.0",
3351− "readable-stream": "^2.3.3",
3352− "stream-browserify": "^2.0.1",
3353− "stream-http": "^2.7.2",
3354− "string_decoder": "^1.0.0",
3355− "timers-browserify": "^2.0.4",
3356− "tty-browserify": "0.0.0",
3357− "url": "^0.11.0",
3358− "util": "^0.10.3",
3359− "vm-browserify": "0.0.4"
3360− },
3361− "dependencies": {
3362− "punycode": {
3363− "version": "1.4.1",
3364− "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
3365− "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
3366− "dev": true
3367− }
3368− }
3369− },
3370− "normalize-path": {
3371− "version": "2.1.1",
3372− "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
3373− "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
3374− "dev": true,
3375− "requires": {
3376− "remove-trailing-separator": "^1.0.1"
3377− }
3378− },
3379− "npm-run-path": {
3380− "version": "2.0.2",
3381− "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
3382− "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
3383− "dev": true,
3384− "requires": {
3385− "path-key": "^2.0.0"
3386− }
3387− },
3388− "number-is-nan": {
3389− "version": "1.0.1",
3390− "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
3391− "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
3392− "dev": true
3393− },
3394− "object-assign": {
3395− "version": "4.1.1",
3396− "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
3397− "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
3398− "dev": true
3399− },
3400− "object-copy": {
3401− "version": "0.1.0",
3402− "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
3403− "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
3404− "dev": true,
3405− "requires": {
3406− "copy-descriptor": "^0.1.0",
3407− "define-property": "^0.2.5",
3408− "kind-of": "^3.0.3"
3409− },
3410− "dependencies": {
3411− "define-property": {
3412− "version": "0.2.5",
3413− "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
3414− "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
3415− "dev": true,
3416− "requires": {
3417− "is-descriptor": "^0.1.0"
3418− }
3419− },
3420− "kind-of": {
3421− "version": "3.2.2",
3422− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
3423− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
3424− "dev": true,
3425− "requires": {
3426− "is-buffer": "^1.1.5"
3427− }
3428− }
3429− }
3430− },
3431− "object-visit": {
3432− "version": "1.0.1",
3433− "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
3434− "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
3435− "dev": true,
3436− "requires": {
3437− "isobject": "^3.0.0"
3438− }
3439− },
3440− "object.pick": {
3441− "version": "1.3.0",
3442− "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
3443− "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
3444− "dev": true,
3445− "requires": {
3446− "isobject": "^3.0.1"
3447− }
3448− },
3449− "obuf": {
3450− "version": "1.1.2",
3451− "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
3452− "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
3453− "dev": true
3454− },
3455− "on-finished": {
3456− "version": "2.3.0",
3457− "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
3458− "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
3459− "dev": true,
3460− "requires": {
3461− "ee-first": "1.1.1"
3462− }
3463− },
3464− "on-headers": {
3465− "version": "1.0.1",
3466− "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
3467− "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=",
3468− "dev": true
3469− },
3470− "once": {
3471− "version": "1.4.0",
3472− "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
3473− "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
3474− "dev": true,
3475− "requires": {
3476− "wrappy": "1"
3477− }
3478− },
3479− "opn": {
3480− "version": "5.4.0",
3481− "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz",
3482− "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==",
3483− "dev": true,
3484− "requires": {
3485− "is-wsl": "^1.1.0"
3486− }
3487− },
3488− "original": {
3489− "version": "1.0.2",
3490− "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
3491− "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
3492− "dev": true,
3493− "requires": {
3494− "url-parse": "^1.4.3"
3495− }
3496− },
3497− "os-browserify": {
3498− "version": "0.3.0",
3499− "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
3500− "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
3501− "dev": true
3502− },
3503− "os-locale": {
3504− "version": "3.0.1",
3505− "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz",
3506− "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==",
3507− "dev": true,
3508− "requires": {
3509− "execa": "^0.10.0",
3510− "lcid": "^2.0.0",
3511− "mem": "^4.0.0"
3512− }
3513− },
3514− "p-defer": {
3515− "version": "1.0.0",
3516− "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
3517− "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
3518− "dev": true
3519− },
3520− "p-finally": {
3521− "version": "1.0.0",
3522− "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
3523− "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
3524− "dev": true
3525− },
3526− "p-is-promise": {
3527− "version": "1.1.0",
3528− "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
3529− "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
3530− "dev": true
3531− },
3532− "p-limit": {
3533− "version": "1.3.0",
3534− "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
3535− "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
3536− "dev": true,
3537− "requires": {
3538− "p-try": "^1.0.0"
3539− }
3540− },
3541− "p-locate": {
3542− "version": "2.0.0",
3543− "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
3544− "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
3545− "dev": true,
3546− "requires": {
3547− "p-limit": "^1.1.0"
3548− }
3549− },
3550− "p-map": {
3551− "version": "1.2.0",
3552− "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
3553− "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
3554− "dev": true
3555− },
3556− "p-try": {
3557− "version": "1.0.0",
3558− "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
3559− "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
3560− "dev": true
3561− },
3562− "pako": {
3563− "version": "1.0.6",
3564− "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
3565− "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==",
3566− "dev": true
3567− },
3568− "parallel-transform": {
3569− "version": "1.1.0",
3570− "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
3571− "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
3572− "dev": true,
3573− "requires": {
3574− "cyclist": "~0.2.2",
3575− "inherits": "^2.0.3",
3576− "readable-stream": "^2.1.5"
3577− }
3578− },
3579− "parse-asn1": {
3580− "version": "5.1.1",
3581− "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
3582− "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
3583− "dev": true,
3584− "requires": {
3585− "asn1.js": "^4.0.0",
3586− "browserify-aes": "^1.0.0",
3587− "create-hash": "^1.1.0",
3588− "evp_bytestokey": "^1.0.0",
3589− "pbkdf2": "^3.0.3"
3590− }
3591− },
3592− "parseurl": {
3593− "version": "1.3.2",
3594− "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
3595− "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
3596− "dev": true
3597− },
3598− "pascalcase": {
3599− "version": "0.1.1",
3600− "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
3601− "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
3602− "dev": true
3603− },
3604− "path-browserify": {
3605− "version": "0.0.0",
3606− "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
3607− "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
3608− "dev": true
3609− },
3610− "path-dirname": {
3611− "version": "1.0.2",
3612− "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
3613− "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
3614− "dev": true
3615− },
3616− "path-exists": {
3617− "version": "3.0.0",
3618− "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
3619− "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
3620− "dev": true
3621− },
3622− "path-is-absolute": {
3623− "version": "1.0.1",
3624− "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
3625− "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
3626− "dev": true
3627− },
3628− "path-is-inside": {
3629− "version": "1.0.2",
3630− "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
3631− "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
3632− "dev": true
3633− },
3634− "path-key": {
3635− "version": "2.0.1",
3636− "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
3637− "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
3638− "dev": true
3639− },
3640− "path-to-regexp": {
3641− "version": "0.1.7",
3642− "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
3643− "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
3644− "dev": true
3645− },
3646− "path-type": {
3647− "version": "3.0.0",
3648− "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
3649− "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
3650− "dev": true,
3651− "requires": {
3652− "pify": "^3.0.0"
3653− }
3654− },
3655− "pbkdf2": {
3656− "version": "3.0.17",
3657− "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
3658− "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
3659− "dev": true,
3660− "requires": {
3661− "create-hash": "^1.1.2",
3662− "create-hmac": "^1.1.4",
3663− "ripemd160": "^2.0.1",
3664− "safe-buffer": "^5.0.1",
3665− "sha.js": "^2.4.8"
3666− }
3667− },
3668− "pify": {
3669− "version": "3.0.0",
3670− "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
3671− "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
3672− "dev": true
3673− },
3674− "pinkie": {
3675− "version": "2.0.4",
3676− "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
3677− "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
3678− "dev": true
3679− },
3680− "pinkie-promise": {
3681− "version": "2.0.1",
3682− "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
3683− "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
3684− "dev": true,
3685− "requires": {
3686− "pinkie": "^2.0.0"
3687− }
3688− },
3689− "pkg-dir": {
3690− "version": "2.0.0",
3691− "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
3692− "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
3693− "dev": true,
3694− "requires": {
3695− "find-up": "^2.1.0"
3696− }
3697− },
3698− "portfinder": {
3699− "version": "1.0.19",
3700− "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.19.tgz",
3701− "integrity": "sha512-23aeQKW9KgHe6citUrG3r9HjeX6vls0h713TAa+CwTKZwNIr/pD2ApaxYF4Um3ZZyq4ar+Siv3+fhoHaIwSOSw==",
3702− "dev": true,
3703− "requires": {
3704− "async": "^1.5.2",
3705− "debug": "^2.2.0",
3706− "mkdirp": "0.5.x"
3707− },
3708− "dependencies": {
3709− "debug": {
3710− "version": "2.6.9",
3711− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
3712− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
3713− "dev": true,
3714− "requires": {
3715− "ms": "2.0.0"
3716− }
3717− }
3718− }
3719− },
3720− "posix-character-classes": {
3721− "version": "0.1.1",
3722− "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
3723− "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
3724− "dev": true
3725− },
3726− "process": {
3727− "version": "0.11.10",
3728− "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
3729− "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
3730− "dev": true
3731− },
3732− "process-nextick-args": {
3733− "version": "2.0.0",
3734− "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
3735− "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
3736− "dev": true
3737− },
3738− "promise-inflight": {
3739− "version": "1.0.1",
3740− "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
3741− "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
3742− "dev": true
3743− },
3744− "proxy-addr": {
3745− "version": "2.0.4",
3746− "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
3747− "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
3748− "dev": true,
3749− "requires": {
3750− "forwarded": "~0.1.2",
3751− "ipaddr.js": "1.8.0"
3752− }
3753− },
3754− "prr": {
3755− "version": "1.0.1",
3756− "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
3757− "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
3758− "dev": true
3759− },
3760− "pseudomap": {
3761− "version": "1.0.2",
3762− "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
3763− "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
3764− "dev": true
3765− },
3766− "public-encrypt": {
3767− "version": "4.0.3",
3768− "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
3769− "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
3770− "dev": true,
3771− "requires": {
3772− "bn.js": "^4.1.0",
3773− "browserify-rsa": "^4.0.0",
3774− "create-hash": "^1.1.0",
3775− "parse-asn1": "^5.0.0",
3776− "randombytes": "^2.0.1",
3777− "safe-buffer": "^5.1.2"
3778− }
3779− },
3780− "pump": {
3781− "version": "2.0.1",
3782− "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
3783− "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
3784− "dev": true,
3785− "requires": {
3786− "end-of-stream": "^1.1.0",
3787− "once": "^1.3.1"
3788− }
3789− },
3790− "pumpify": {
3791− "version": "1.5.1",
3792− "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
3793− "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
3794− "dev": true,
3795− "requires": {
3796− "duplexify": "^3.6.0",
3797− "inherits": "^2.0.3",
3798− "pump": "^2.0.0"
3799− }
3800− },
3801− "punycode": {
3802− "version": "2.1.1",
3803− "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
3804− "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
3805− "dev": true
3806− },
3807− "qs": {
3808− "version": "6.5.2",
3809− "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
3810− "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
3811− "dev": true
3812− },
3813− "querystring": {
3814− "version": "0.2.0",
3815− "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
3816− "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
3817− "dev": true
3818− },
3819− "querystring-es3": {
3820− "version": "0.2.1",
3821− "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
3822− "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
3823− "dev": true
3824− },
3825− "querystringify": {
3826− "version": "2.1.0",
3827− "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz",
3828− "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
3829− "dev": true
3830− },
3831− "randombytes": {
3832− "version": "2.0.6",
3833− "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz",
3834− "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==",
3835− "dev": true,
3836− "requires": {
3837− "safe-buffer": "^5.1.0"
3838− }
3839− },
3840− "randomfill": {
3841− "version": "1.0.4",
3842− "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
3843− "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
3844− "dev": true,
3845− "requires": {
3846− "randombytes": "^2.0.5",
3847− "safe-buffer": "^5.1.0"
3848− }
3849− },
3850− "range-parser": {
3851− "version": "1.2.0",
3852− "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
3853− "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
3854− "dev": true
3855− },
3856− "raw-body": {
3857− "version": "2.3.3",
3858− "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
3859− "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
3860− "dev": true,
3861− "requires": {
3862− "bytes": "3.0.0",
3863− "http-errors": "1.6.3",
3864− "iconv-lite": "0.4.23",
3865− "unpipe": "1.0.0"
3866− }
3867− },
3868− "readable-stream": {
3869− "version": "2.3.6",
3870− "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
3871− "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
3872− "dev": true,
3873− "requires": {
3874− "core-util-is": "~1.0.0",
3875− "inherits": "~2.0.3",
3876− "isarray": "~1.0.0",
3877− "process-nextick-args": "~2.0.0",
3878− "safe-buffer": "~5.1.1",
3879− "string_decoder": "~1.1.1",
3880− "util-deprecate": "~1.0.1"
3881− }
3882− },
3883− "readdirp": {
3884− "version": "2.1.0",
3885− "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
3886− "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
3887− "dev": true,
3888− "requires": {
3889− "graceful-fs": "^4.1.2",
3890− "minimatch": "^3.0.2",
3891− "readable-stream": "^2.0.2",
3892− "set-immediate-shim": "^1.0.1"
3893− }
3894− },
3895− "regex-not": {
3896− "version": "1.0.2",
3897− "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
3898− "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
3899− "dev": true,
3900− "requires": {
3901− "extend-shallow": "^3.0.2",
3902− "safe-regex": "^1.1.0"
3903− }
3904− },
3905− "remove-trailing-separator": {
3906− "version": "1.1.0",
3907− "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
3908− "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
3909− "dev": true
3910− },
3911− "repeat-element": {
3912− "version": "1.1.2",
3913− "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
3914− "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
3915− "dev": true
3916− },
3917− "repeat-string": {
3918− "version": "1.6.1",
3919− "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
3920− "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
3921− "dev": true
3922− },
3923− "require-directory": {
3924− "version": "2.1.1",
3925− "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
3926− "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
3927− "dev": true
3928− },
3929− "require-main-filename": {
3930− "version": "1.0.1",
3931− "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
3932− "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
3933− "dev": true
3934− },
3935− "requires-port": {
3936− "version": "1.0.0",
3937− "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
3938− "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
3939− "dev": true
3940− },
3941− "resolve-cwd": {
3942− "version": "2.0.0",
3943− "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
3944− "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
3945− "dev": true,
3946− "requires": {
3947− "resolve-from": "^3.0.0"
3948− }
3949− },
3950− "resolve-from": {
3951− "version": "3.0.0",
3952− "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
3953− "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
3954− "dev": true
3955− },
3956− "resolve-url": {
3957− "version": "0.2.1",
3958− "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
3959− "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
3960− "dev": true
3961− },
3962− "ret": {
3963− "version": "0.1.15",
3964− "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
3965− "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
3966− "dev": true
3967− },
3968− "rimraf": {
3969− "version": "2.6.2",
3970− "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
3971− "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
3972− "dev": true,
3973− "requires": {
3974− "glob": "^7.0.5"
3975− }
3976− },
3977− "ripemd160": {
3978− "version": "2.0.2",
3979− "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
3980− "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
3981− "dev": true,
3982− "requires": {
3983− "hash-base": "^3.0.0",
3984− "inherits": "^2.0.1"
3985− }
3986− },
3987− "run-queue": {
3988− "version": "1.0.3",
3989− "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
3990− "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
3991− "dev": true,
3992− "requires": {
3993− "aproba": "^1.1.1"
3994− }
3995− },
3996− "safe-buffer": {
3997− "version": "5.1.2",
3998− "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
3999− "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
4000− "dev": true
4001− },
4002− "safe-regex": {
4003− "version": "1.1.0",
4004− "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
4005− "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
4006− "dev": true,
4007− "requires": {
4008− "ret": "~0.1.10"
4009− }
4010− },
4011− "safer-buffer": {
4012− "version": "2.1.2",
4013− "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
4014− "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
4015− "dev": true
4016− },
4017− "schema-utils": {
4018− "version": "0.4.7",
4019− "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
4020− "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
4021− "dev": true,
4022− "requires": {
4023− "ajv": "^6.1.0",
4024− "ajv-keywords": "^3.1.0"
4025− }
4026− },
4027− "select-hose": {
4028− "version": "2.0.0",
4029− "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
4030− "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
4031− "dev": true
4032− },
4033− "selfsigned": {
4034− "version": "1.10.4",
4035− "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz",
4036− "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==",
4037− "dev": true,
4038− "requires": {
4039− "node-forge": "0.7.5"
4040− }
4041− },
4042− "semver": {
4043− "version": "5.5.0",
4044− "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
4045− "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
4046− "dev": true
4047− },
4048− "send": {
4049− "version": "0.16.2",
4050− "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
4051− "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
4052− "dev": true,
4053− "requires": {
4054− "debug": "2.6.9",
4055− "depd": "~1.1.2",
4056− "destroy": "~1.0.4",
4057− "encodeurl": "~1.0.2",
4058− "escape-html": "~1.0.3",
4059− "etag": "~1.8.1",
4060− "fresh": "0.5.2",
4061− "http-errors": "~1.6.2",
4062− "mime": "1.4.1",
4063− "ms": "2.0.0",
4064− "on-finished": "~2.3.0",
4065− "range-parser": "~1.2.0",
4066− "statuses": "~1.4.0"
4067− },
4068− "dependencies": {
4069− "debug": {
4070− "version": "2.6.9",
4071− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
4072− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
4073− "dev": true,
4074− "requires": {
4075− "ms": "2.0.0"
4076− }
4077− }
4078− }
4079− },
4080− "serialize-javascript": {
4081− "version": "1.5.0",
4082− "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz",
4083− "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==",
4084− "dev": true
4085− },
4086− "serve-index": {
4087− "version": "1.9.1",
4088− "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
4089− "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
4090− "dev": true,
4091− "requires": {
4092− "accepts": "~1.3.4",
4093− "batch": "0.6.1",
4094− "debug": "2.6.9",
4095− "escape-html": "~1.0.3",
4096− "http-errors": "~1.6.2",
4097− "mime-types": "~2.1.17",
4098− "parseurl": "~1.3.2"
4099− },
4100− "dependencies": {
4101− "debug": {
4102− "version": "2.6.9",
4103− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
4104− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
4105− "dev": true,
4106− "requires": {
4107− "ms": "2.0.0"
4108− }
4109− }
4110− }
4111− },
4112− "serve-static": {
4113− "version": "1.13.2",
4114− "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
4115− "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
4116− "dev": true,
4117− "requires": {
4118− "encodeurl": "~1.0.2",
4119− "escape-html": "~1.0.3",
4120− "parseurl": "~1.3.2",
4121− "send": "0.16.2"
4122− }
4123− },
4124− "set-blocking": {
4125− "version": "2.0.0",
4126− "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
4127− "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
4128− "dev": true
4129− },
4130− "set-immediate-shim": {
4131− "version": "1.0.1",
4132− "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
4133− "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
4134− "dev": true
4135− },
4136− "set-value": {
4137− "version": "2.0.0",
4138− "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
4139− "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
4140− "dev": true,
4141− "requires": {
4142− "extend-shallow": "^2.0.1",
4143− "is-extendable": "^0.1.1",
4144− "is-plain-object": "^2.0.3",
4145− "split-string": "^3.0.1"
4146− },
4147− "dependencies": {
4148− "extend-shallow": {
4149− "version": "2.0.1",
4150− "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
4151− "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
4152− "dev": true,
4153− "requires": {
4154− "is-extendable": "^0.1.0"
4155− }
4156− }
4157− }
4158− },
4159− "setimmediate": {
4160− "version": "1.0.5",
4161− "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
4162− "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
4163− "dev": true
4164− },
4165− "setprototypeof": {
4166− "version": "1.1.0",
4167− "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
4168− "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
4169− "dev": true
4170− },
4171− "sha.js": {
4172− "version": "2.4.11",
4173− "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
4174− "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
4175− "dev": true,
4176− "requires": {
4177− "inherits": "^2.0.1",
4178− "safe-buffer": "^5.0.1"
4179− }
4180− },
4181− "shebang-command": {
4182− "version": "1.2.0",
4183− "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
4184− "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
4185− "dev": true,
4186− "requires": {
4187− "shebang-regex": "^1.0.0"
4188− }
4189− },
4190− "shebang-regex": {
4191− "version": "1.0.0",
4192− "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
4193− "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
4194− "dev": true
4195− },
4196− "signal-exit": {
4197− "version": "3.0.2",
4198− "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
4199− "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
4200− "dev": true
4201− },
4202− "slash": {
4203− "version": "1.0.0",
4204− "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
4205− "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
4206− "dev": true
4207− },
4208− "snapdragon": {
4209− "version": "0.8.2",
4210− "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
4211− "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
4212− "dev": true,
4213− "requires": {
4214− "base": "^0.11.1",
4215− "debug": "^2.2.0",
4216− "define-property": "^0.2.5",
4217− "extend-shallow": "^2.0.1",
4218− "map-cache": "^0.2.2",
4219− "source-map": "^0.5.6",
4220− "source-map-resolve": "^0.5.0",
4221− "use": "^3.1.0"
4222− },
4223− "dependencies": {
4224− "debug": {
4225− "version": "2.6.9",
4226− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
4227− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
4228− "dev": true,
4229− "requires": {
4230− "ms": "2.0.0"
4231− }
4232− },
4233− "define-property": {
4234− "version": "0.2.5",
4235− "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
4236− "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
4237− "dev": true,
4238− "requires": {
4239− "is-descriptor": "^0.1.0"
4240− }
4241− },
4242− "extend-shallow": {
4243− "version": "2.0.1",
4244− "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
4245− "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
4246− "dev": true,
4247− "requires": {
4248− "is-extendable": "^0.1.0"
4249− }
4250− }
4251− }
4252− },
4253− "snapdragon-node": {
4254− "version": "2.1.1",
4255− "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
4256− "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
4257− "dev": true,
4258− "requires": {
4259− "define-property": "^1.0.0",
4260− "isobject": "^3.0.0",
4261− "snapdragon-util": "^3.0.1"
4262− },
4263− "dependencies": {
4264− "define-property": {
4265− "version": "1.0.0",
4266− "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
4267− "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
4268− "dev": true,
4269− "requires": {
4270− "is-descriptor": "^1.0.0"
4271− }
4272− },
4273− "is-accessor-descriptor": {
4274− "version": "1.0.0",
4275− "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
4276− "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
4277− "dev": true,
4278− "requires": {
4279− "kind-of": "^6.0.0"
4280− }
4281− },
4282− "is-data-descriptor": {
4283− "version": "1.0.0",
4284− "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
4285− "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
4286− "dev": true,
4287− "requires": {
4288− "kind-of": "^6.0.0"
4289− }
4290− },
4291− "is-descriptor": {
4292− "version": "1.0.2",
4293− "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
4294− "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
4295− "dev": true,
4296− "requires": {
4297− "is-accessor-descriptor": "^1.0.0",
4298− "is-data-descriptor": "^1.0.0",
4299− "kind-of": "^6.0.2"
4300− }
4301− }
4302− }
4303− },
4304− "snapdragon-util": {
4305− "version": "3.0.1",
4306− "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
4307− "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
4308− "dev": true,
4309− "requires": {
4310− "kind-of": "^3.2.0"
4311− },
4312− "dependencies": {
4313− "kind-of": {
4314− "version": "3.2.2",
4315− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
4316− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
4317− "dev": true,
4318− "requires": {
4319− "is-buffer": "^1.1.5"
4320− }
4321− }
4322− }
4323− },
4324− "sockjs": {
4325− "version": "0.3.19",
4326− "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
4327− "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
4328− "dev": true,
4329− "requires": {
4330− "faye-websocket": "^0.10.0",
4331− "uuid": "^3.0.1"
4332− }
4333− },
4334− "sockjs-client": {
4335− "version": "1.3.0",
4336− "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz",
4337− "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==",
4338− "dev": true,
4339− "requires": {
4340− "debug": "^3.2.5",
4341− "eventsource": "^1.0.7",
4342− "faye-websocket": "~0.11.1",
4343− "inherits": "^2.0.3",
4344− "json3": "^3.3.2",
4345− "url-parse": "^1.4.3"
4346− },
4347− "dependencies": {
4348− "faye-websocket": {
4349− "version": "0.11.1",
4350− "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz",
4351− "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=",
4352− "dev": true,
4353− "requires": {
4354− "websocket-driver": ">=0.5.1"
4355− }
4356− }
4357− }
4358− },
4359− "source-list-map": {
4360− "version": "2.0.1",
4361− "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
4362− "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
4363− "dev": true
4364− },
4365− "source-map": {
4366− "version": "0.5.7",
4367− "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
4368− "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
4369− "dev": true
4370− },
4371− "source-map-resolve": {
4372− "version": "0.5.2",
4373− "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
4374− "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
4375− "dev": true,
4376− "requires": {
4377− "atob": "^2.1.1",
4378− "decode-uri-component": "^0.2.0",
4379− "resolve-url": "^0.2.1",
4380− "source-map-url": "^0.4.0",
4381− "urix": "^0.1.0"
4382− }
4383− },
4384− "source-map-url": {
4385− "version": "0.4.0",
4386− "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
4387− "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
4388− "dev": true
4389− },
4390− "spdy": {
4391− "version": "3.4.7",
4392− "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz",
4393− "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=",
4394− "dev": true,
4395− "requires": {
4396− "debug": "^2.6.8",
4397− "handle-thing": "^1.2.5",
4398− "http-deceiver": "^1.2.7",
4399− "safe-buffer": "^5.0.1",
4400− "select-hose": "^2.0.0",
4401− "spdy-transport": "^2.0.18"
4402− },
4403− "dependencies": {
4404− "debug": {
4405− "version": "2.6.9",
4406− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
4407− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
4408− "dev": true,
4409− "requires": {
4410− "ms": "2.0.0"
4411− }
4412− }
4413− }
4414− },
4415− "spdy-transport": {
4416− "version": "2.1.0",
4417− "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz",
4418− "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==",
4419− "dev": true,
4420− "requires": {
4421− "debug": "^2.6.8",
4422− "detect-node": "^2.0.3",
4423− "hpack.js": "^2.1.6",
4424− "obuf": "^1.1.1",
4425− "readable-stream": "^2.2.9",
4426− "safe-buffer": "^5.0.1",
4427− "wbuf": "^1.7.2"
4428− },
4429− "dependencies": {
4430− "debug": {
4431− "version": "2.6.9",
4432− "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
4433− "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
4434− "dev": true,
4435− "requires": {
4436− "ms": "2.0.0"
4437− }
4438− }
4439− }
4440− },
4441− "split-string": {
4442− "version": "3.1.0",
4443− "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
4444− "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
4445− "dev": true,
4446− "requires": {
4447− "extend-shallow": "^3.0.0"
4448− }
4449− },
4450− "ssri": {
4451− "version": "5.3.0",
4452− "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz",
4453− "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==",
4454− "dev": true,
4455− "requires": {
4456− "safe-buffer": "^5.1.1"
4457− }
4458− },
4459− "static-extend": {
4460− "version": "0.1.2",
4461− "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
4462− "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
4463− "dev": true,
4464− "requires": {
4465− "define-property": "^0.2.5",
4466− "object-copy": "^0.1.0"
4467− },
4468− "dependencies": {
4469− "define-property": {
4470− "version": "0.2.5",
4471− "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
4472− "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
4473− "dev": true,
4474− "requires": {
4475− "is-descriptor": "^0.1.0"
4476− }
4477− }
4478− }
4479− },
4480− "statuses": {
4481− "version": "1.4.0",
4482− "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
4483− "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
4484− "dev": true
4485− },
4486− "stream-browserify": {
4487− "version": "2.0.1",
4488− "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
4489− "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
4490− "dev": true,
4491− "requires": {
4492− "inherits": "~2.0.1",
4493− "readable-stream": "^2.0.2"
4494− }
4495− },
4496− "stream-each": {
4497− "version": "1.2.3",
4498− "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
4499− "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
4500− "dev": true,
4501− "requires": {
4502− "end-of-stream": "^1.1.0",
4503− "stream-shift": "^1.0.0"
4504− }
4505− },
4506− "stream-http": {
4507− "version": "2.8.3",
4508− "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
4509− "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
4510− "dev": true,
4511− "requires": {
4512− "builtin-status-codes": "^3.0.0",
4513− "inherits": "^2.0.1",
4514− "readable-stream": "^2.3.6",
4515− "to-arraybuffer": "^1.0.0",
4516− "xtend": "^4.0.0"
4517− }
4518− },
4519− "stream-shift": {
4520− "version": "1.0.0",
4521− "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
4522− "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
4523− "dev": true
4524− },
4525− "string-width": {
4526− "version": "2.1.1",
4527− "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
4528− "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
4529− "dev": true,
4530− "requires": {
4531− "is-fullwidth-code-point": "^2.0.0",
4532− "strip-ansi": "^4.0.0"
4533− }
4534− },
4535− "string_decoder": {
4536− "version": "1.1.1",
4537− "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
4538− "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
4539− "dev": true,
4540− "requires": {
4541− "safe-buffer": "~5.1.0"
4542− }
4543− },
4544− "strip-ansi": {
4545− "version": "4.0.0",
4546− "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
4547− "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
4548− "dev": true,
4549− "requires": {
4550− "ansi-regex": "^3.0.0"
4551− }
4552− },
4553− "strip-eof": {
4554− "version": "1.0.0",
4555− "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
4556− "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
4557− "dev": true
4558− },
4559− "supports-color": {
4560− "version": "5.4.0",
4561− "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
4562− "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
4563− "dev": true,
4564− "requires": {
4565− "has-flag": "^3.0.0"
4566− }
4567− },
4568− "tapable": {
4569− "version": "1.0.0",
4570− "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz",
4571− "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==",
4572− "dev": true
4573− },
4574− "through2": {
4575− "version": "2.0.3",
4576− "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
4577− "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
4578− "dev": true,
4579− "requires": {
4580− "readable-stream": "^2.1.5",
4581− "xtend": "~4.0.1"
4582− }
4583− },
4584− "thunky": {
4585− "version": "1.0.3",
4586− "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz",
4587− "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==",
4588− "dev": true
4589− },
4590− "timers-browserify": {
4591− "version": "2.0.10",
4592− "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz",
4593− "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==",
4594− "dev": true,
4595− "requires": {
4596− "setimmediate": "^1.0.4"
4597− }
4598− },
4599− "to-arraybuffer": {
4600− "version": "1.0.1",
4601− "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
4602− "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
4603− "dev": true
4604− },
4605− "to-object-path": {
4606− "version": "0.3.0",
4607− "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
4608− "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
4609− "dev": true,
4610− "requires": {
4611− "kind-of": "^3.0.2"
4612− },
4613− "dependencies": {
4614− "kind-of": {
4615− "version": "3.2.2",
4616− "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
4617− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
4618− "dev": true,
4619− "requires": {
4620− "is-buffer": "^1.1.5"
4621− }
4622− }
4623− }
4624− },
4625− "to-regex": {
4626− "version": "3.0.2",
4627− "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
4628− "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
4629− "dev": true,
4630− "requires": {
4631− "define-property": "^2.0.2",
4632− "extend-shallow": "^3.0.2",
4633− "regex-not": "^1.0.2",
4634− "safe-regex": "^1.1.0"
4635− }
4636− },
4637− "to-regex-range": {
4638− "version": "2.1.1",
4639− "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
4640− "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
4641− "dev": true,
4642− "requires": {
4643− "is-number": "^3.0.0",
4644− "repeat-string": "^1.6.1"
4645− }
4646− },
4647− "tslib": {
4648− "version": "1.9.3",
4649− "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
4650− "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
4651− "dev": true
4652− },
4653− "tty-browserify": {
4654− "version": "0.0.0",
4655− "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
4656− "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
4657− "dev": true
4658− },
4659− "type-is": {
4660− "version": "1.6.16",
4661− "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
4662− "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
4663− "dev": true,
4664− "requires": {
4665− "media-typer": "0.3.0",
4666− "mime-types": "~2.1.18"
4667− }
4668− },
4669− "typedarray": {
4670− "version": "0.0.6",
4671− "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
4672− "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
4673− "dev": true
4674− },
4675− "uglify-es": {
4676− "version": "3.3.9",
4677− "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
4678− "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
4679− "dev": true,
4680− "requires": {
4681− "commander": "~2.13.0",
4682− "source-map": "~0.6.1"
4683− },
4684− "dependencies": {
4685− "source-map": {
4686− "version": "0.6.1",
4687− "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
4688− "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
4689− "dev": true
4690− }
4691− }
4692− },
4693− "uglifyjs-webpack-plugin": {
4694− "version": "1.3.0",
4695− "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz",
4696− "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==",
4697− "dev": true,
4698− "requires": {
4699− "cacache": "^10.0.4",
4700− "find-cache-dir": "^1.0.0",
4701− "schema-utils": "^0.4.5",
4702− "serialize-javascript": "^1.4.0",
4703− "source-map": "^0.6.1",
4704− "uglify-es": "^3.3.4",
4705− "webpack-sources": "^1.1.0",
4706− "worker-farm": "^1.5.2"
4707− },
4708− "dependencies": {
4709− "source-map": {
4710− "version": "0.6.1",
4711− "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
4712− "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
4713− "dev": true
4714− }
4715− }
4716− },
4717− "union-value": {
4718− "version": "1.0.0",
4719− "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
4720− "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
4721− "dev": true,
4722− "requires": {
4723− "arr-union": "^3.1.0",
4724− "get-value": "^2.0.6",
4725− "is-extendable": "^0.1.1",
4726− "set-value": "^0.4.3"
4727− },
4728− "dependencies": {
4729− "extend-shallow": {
4730− "version": "2.0.1",
4731− "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
4732− "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
4733− "dev": true,
4734− "requires": {
4735− "is-extendable": "^0.1.0"
4736− }
4737− },
4738− "set-value": {
4739− "version": "0.4.3",
4740− "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
4741− "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
4742− "dev": true,
4743− "requires": {
4744− "extend-shallow": "^2.0.1",
4745− "is-extendable": "^0.1.1",
4746− "is-plain-object": "^2.0.1",
4747− "to-object-path": "^0.3.0"
4748− }
4749− }
4750− }
4751− },
4752− "unique-filename": {
4753− "version": "1.1.0",
4754− "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz",
4755− "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=",
4756− "dev": true,
4757− "requires": {
4758− "unique-slug": "^2.0.0"
4759− }
4760− },
4761− "unique-slug": {
4762− "version": "2.0.0",
4763− "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz",
4764− "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
4765− "dev": true,
4766− "requires": {
4767− "imurmurhash": "^0.1.4"
4768− }
4769− },
4770− "unpipe": {
4771− "version": "1.0.0",
4772− "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
4773− "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
4774− "dev": true
4775− },
4776− "unset-value": {
4777− "version": "1.0.0",
4778− "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
4779− "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
4780− "dev": true,
4781− "requires": {
4782− "has-value": "^0.3.1",
4783− "isobject": "^3.0.0"
4784− },
4785− "dependencies": {
4786− "has-value": {
4787− "version": "0.3.1",
4788− "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
4789− "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
4790− "dev": true,
4791− "requires": {
4792− "get-value": "^2.0.3",
4793− "has-values": "^0.1.4",
4794− "isobject": "^2.0.0"
4795− },
4796− "dependencies": {
4797− "isobject": {
4798− "version": "2.1.0",
4799− "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
4800− "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
4801− "dev": true,
4802− "requires": {
4803− "isarray": "1.0.0"
4804− }
4805− }
4806− }
4807− },
4808− "has-values": {
4809− "version": "0.1.4",
4810− "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
4811− "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
4812− "dev": true
4813− }
4814− }
4815− },
4816− "upath": {
4817− "version": "1.1.0",
4818− "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
4819− "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==",
4820− "dev": true
4821− },
4822− "uri-js": {
4823− "version": "4.2.2",
4824− "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
4825− "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
4826− "dev": true,
4827− "requires": {
4828− "punycode": "^2.1.0"
4829− }
4830− },
4831− "urix": {
4832− "version": "0.1.0",
4833− "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
4834− "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
4835− "dev": true
4836− },
4837− "url": {
4838− "version": "0.11.0",
4839− "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
4840− "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
4841− "dev": true,
4842− "requires": {
4843− "punycode": "1.3.2",
4844− "querystring": "0.2.0"
4845− },
4846− "dependencies": {
4847− "punycode": {
4848− "version": "1.3.2",
4849− "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
4850− "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
4851− "dev": true
4852− }
4853− }
4854− },
4855− "url-parse": {
4856− "version": "1.4.3",
4857− "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz",
4858− "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==",
4859− "dev": true,
4860− "requires": {
4861− "querystringify": "^2.0.0",
4862− "requires-port": "^1.0.0"
4863− }
4864− },
4865− "use": {
4866− "version": "3.1.1",
4867− "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
4868− "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
4869− "dev": true
4870− },
4871− "util": {
4872− "version": "0.10.4",
4873− "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
4874− "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
4875− "dev": true,
4876− "requires": {
4877− "inherits": "2.0.3"
4878− }
4879− },
4880− "util-deprecate": {
4881− "version": "1.0.2",
4882− "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
4883− "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
4884− "dev": true
4885− },
4886− "utils-merge": {
4887− "version": "1.0.1",
4888− "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
4889− "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
4890− "dev": true
4891− },
4892− "uuid": {
4893− "version": "3.3.2",
4894− "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
4895− "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
4896− "dev": true
4897− },
4898− "v8-compile-cache": {
4899− "version": "2.0.2",
4900− "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz",
4901− "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==",
4902− "dev": true
4903− },
4904− "vary": {
4905− "version": "1.1.2",
4906− "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
4907− "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
4908− "dev": true
4909− },
4910− "vm-browserify": {
4911− "version": "0.0.4",
4912− "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
4913− "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
4914− "dev": true,
4915− "requires": {
4916− "indexof": "0.0.1"
4917− }
4918− },
4919− "watchpack": {
4920− "version": "1.6.0",
4921− "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
4922− "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
4923− "dev": true,
4924− "requires": {
4925− "chokidar": "^2.0.2",
4926− "graceful-fs": "^4.1.2",
4927− "neo-async": "^2.5.0"
4928− }
4929− },
4930− "wbuf": {
4931− "version": "1.7.3",
4932− "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
4933− "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
4934− "dev": true,
4935− "requires": {
4936− "minimalistic-assert": "^1.0.0"
4937− }
4938− },
4939− "webpack": {
4940− "version": "4.22.0",
4941− "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.22.0.tgz",
4942− "integrity": "sha512-2+3EYFqyhPl12buLQ42QPHEEh8BHn3P9ipRvGRHhdfKJ1u9svhZ3QjhIoEdL5SeIhL5gfOZVbBnartYEabkEsg==",
4943− "dev": true,
4944− "requires": {
4945− "@webassemblyjs/ast": "1.7.8",
4946− "@webassemblyjs/helper-module-context": "1.7.8",
4947− "@webassemblyjs/wasm-edit": "1.7.8",
4948− "@webassemblyjs/wasm-parser": "1.7.8",
4949− "acorn": "^5.6.2",
4950− "acorn-dynamic-import": "^3.0.0",
4951− "ajv": "^6.1.0",
4952− "ajv-keywords": "^3.1.0",
4953− "chrome-trace-event": "^1.0.0",
4954− "enhanced-resolve": "^4.1.0",
4955− "eslint-scope": "^4.0.0",
4956− "json-parse-better-errors": "^1.0.2",
4957− "loader-runner": "^2.3.0",
4958− "loader-utils": "^1.1.0",
4959− "memory-fs": "~0.4.1",
4960− "micromatch": "^3.1.8",
4961− "mkdirp": "~0.5.0",
4962− "neo-async": "^2.5.0",
4963− "node-libs-browser": "^2.0.0",
4964− "schema-utils": "^0.4.4",
4965− "tapable": "^1.1.0",
4966− "uglifyjs-webpack-plugin": "^1.2.4",
4967− "watchpack": "^1.5.0",
4968− "webpack-sources": "^1.3.0"
4969− },
4970− "dependencies": {
4971− "tapable": {
4972− "version": "1.1.0",
4973− "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz",
4974− "integrity": "sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==",
4975− "dev": true
4976− }
4977− }
4978− },
4979− "webpack-cli": {
4980− "version": "3.1.2",
4981− "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz",
4982− "integrity": "sha512-Cnqo7CeqeSvC6PTdts+dywNi5CRlIPbLx1AoUPK2T6vC1YAugMG3IOoO9DmEscd+Dghw7uRlnzV1KwOe5IrtgQ==",
4983− "dev": true,
4984− "requires": {
4985− "chalk": "^2.4.1",
4986− "cross-spawn": "^6.0.5",
4987− "enhanced-resolve": "^4.1.0",
4988− "global-modules-path": "^2.3.0",
4989− "import-local": "^2.0.0",
4990− "interpret": "^1.1.0",
4991− "loader-utils": "^1.1.0",
4992− "supports-color": "^5.5.0",
4993− "v8-compile-cache": "^2.0.2",
4994− "yargs": "^12.0.2"
4995− },
4996− "dependencies": {
4997− "supports-color": {
4998− "version": "5.5.0",
4999− "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
5000− "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
5001− "dev": true,
5002− "requires": {
5003− "has-flag": "^3.0.0"
5004− }
5005− }
5006− }
5007− },
5008− "webpack-dev-middleware": {
5009− "version": "3.4.0",
5010− "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz",
5011− "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==",
5012− "dev": true,
5013− "requires": {
5014− "memory-fs": "~0.4.1",
5015− "mime": "^2.3.1",
5016− "range-parser": "^1.0.3",
5017− "webpack-log": "^2.0.0"
5018− },
5019− "dependencies": {
5020− "mime": {
5021− "version": "2.3.1",
5022− "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
5023− "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==",
5024− "dev": true
5025− }
5026− }
5027− },
5028− "webpack-dev-server": {
5029− "version": "3.1.10",
5030− "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.10.tgz",
5031− "integrity": "sha512-RqOAVjfqZJtQcB0LmrzJ5y4Jp78lv9CK0MZ1YJDTaTmedMZ9PU9FLMQNrMCfVu8hHzaVLVOJKBlGEHMN10z+ww==",
5032− "dev": true,
5033− "requires": {
5034− "ansi-html": "0.0.7",
5035− "bonjour": "^3.5.0",
5036− "chokidar": "^2.0.0",
5037− "compression": "^1.5.2",
5038− "connect-history-api-fallback": "^1.3.0",
5039− "debug": "^3.1.0",
5040− "del": "^3.0.0",
5041− "express": "^4.16.2",
5042− "html-entities": "^1.2.0",
5043− "http-proxy-middleware": "~0.18.0",
5044− "import-local": "^2.0.0",
5045− "internal-ip": "^3.0.1",
5046− "ip": "^1.1.5",
5047− "killable": "^1.0.0",
5048− "loglevel": "^1.4.1",
5049− "opn": "^5.1.0",
5050− "portfinder": "^1.0.9",
5051− "schema-utils": "^1.0.0",
5052− "selfsigned": "^1.9.1",
5053− "serve-index": "^1.7.2",
5054− "sockjs": "0.3.19",
5055− "sockjs-client": "1.3.0",
5056− "spdy": "^3.4.1",
5057− "strip-ansi": "^3.0.0",
5058− "supports-color": "^5.1.0",
5059− "webpack-dev-middleware": "3.4.0",
5060− "webpack-log": "^2.0.0",
5061− "yargs": "12.0.2"
5062− },
5063− "dependencies": {
5064− "ansi-regex": {
5065− "version": "2.1.1",
5066− "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
5067− "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
5068− "dev": true
5069− },
5070− "schema-utils": {
5071− "version": "1.0.0",
5072− "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
5073− "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
5074− "dev": true,
5075− "requires": {
5076− "ajv": "^6.1.0",
5077− "ajv-errors": "^1.0.0",
5078− "ajv-keywords": "^3.1.0"
5079− }
5080− },
5081− "strip-ansi": {
5082− "version": "3.0.1",
5083− "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
5084− "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
5085− "dev": true,
5086− "requires": {
5087− "ansi-regex": "^2.0.0"
5088− }
5089− }
5090− }
5091− },
5092− "webpack-log": {
5093− "version": "2.0.0",
5094− "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
5095− "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
5096− "dev": true,
5097− "requires": {
5098− "ansi-colors": "^3.0.0",
5099− "uuid": "^3.3.2"
5100− }
5101− },
5102− "webpack-sources": {
5103− "version": "1.3.0",
5104− "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz",
5105− "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==",
5106− "dev": true,
5107− "requires": {
5108− "source-list-map": "^2.0.0",
5109− "source-map": "~0.6.1"
5110− },
5111− "dependencies": {
5112− "source-map": {
5113− "version": "0.6.1",
5114− "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
5115− "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
5116− "dev": true
5117− }
5118− }
5119− },
5120− "websocket-driver": {
5121− "version": "0.7.0",
5122− "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
5123− "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=",
5124− "dev": true,
5125− "requires": {
5126− "http-parser-js": ">=0.4.0",
5127− "websocket-extensions": ">=0.1.1"
5128− }
5129− },
5130− "websocket-extensions": {
5131− "version": "0.1.3",
5132− "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
5133− "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
5134− "dev": true
5135− },
5136− "which": {
5137− "version": "1.3.1",
5138− "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
5139− "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
5140− "dev": true,
5141− "requires": {
5142− "isexe": "^2.0.0"
5143− }
5144− },
5145− "which-module": {
5146− "version": "2.0.0",
5147− "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
5148− "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
5149− "dev": true
5150− },
5151− "worker-farm": {
5152− "version": "1.6.0",
5153− "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz",
5154− "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==",
5155− "dev": true,
5156− "requires": {
5157− "errno": "~0.1.7"
5158− }
5159− },
5160− "wrap-ansi": {
5161− "version": "2.1.0",
5162− "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
5163− "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
5164− "dev": true,
5165− "requires": {
5166− "string-width": "^1.0.1",
5167− "strip-ansi": "^3.0.1"
5168− },
5169− "dependencies": {
5170− "ansi-regex": {
5171− "version": "2.1.1",
5172− "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
5173− "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
5174− "dev": true
5175− },
5176− "is-fullwidth-code-point": {
5177− "version": "1.0.0",
5178− "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
5179− "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
5180− "dev": true,
5181− "requires": {
5182− "number-is-nan": "^1.0.0"
5183− }
5184− },
5185− "string-width": {
5186− "version": "1.0.2",
5187− "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
5188− "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
5189− "dev": true,
5190− "requires": {
5191− "code-point-at": "^1.0.0",
5192− "is-fullwidth-code-point": "^1.0.0",
5193− "strip-ansi": "^3.0.0"
5194− }
5195− },
5196− "strip-ansi": {
5197− "version": "3.0.1",
5198− "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
5199− "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
5200− "dev": true,
5201− "requires": {
5202− "ansi-regex": "^2.0.0"
5203− }
5204− }
5205− }
5206− },
5207− "wrappy": {
5208− "version": "1.0.2",
5209− "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
5210− "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
5211− "dev": true
5212− },
5213− "xregexp": {
5214− "version": "4.0.0",
5215− "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
5216− "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==",
5217− "dev": true
5218− },
5219− "xtend": {
5220− "version": "4.0.1",
5221− "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
5222− "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
5223− "dev": true
5224− },
5225− "y18n": {
5226− "version": "4.0.0",
5227− "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
5228− "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
5229− "dev": true
5230− },
5231− "yallist": {
5232− "version": "2.1.2",
5233− "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
5234− "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
5235− "dev": true
5236− },
5237− "yargs": {
5238− "version": "12.0.2",
5239− "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz",
5240− "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==",
5241− "dev": true,
5242− "requires": {
5243− "cliui": "^4.0.0",
5244− "decamelize": "^2.0.0",
5245− "find-up": "^3.0.0",
5246− "get-caller-file": "^1.0.1",
5247− "os-locale": "^3.0.0",
5248− "require-directory": "^2.1.1",
5249− "require-main-filename": "^1.0.1",
5250− "set-blocking": "^2.0.0",
5251− "string-width": "^2.0.0",
5252− "which-module": "^2.0.0",
5253− "y18n": "^3.2.1 || ^4.0.0",
5254− "yargs-parser": "^10.1.0"
5255− },
5256− "dependencies": {
5257− "find-up": {
5258− "version": "3.0.0",
5259− "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
5260− "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
5261− "dev": true,
5262− "requires": {
5263− "locate-path": "^3.0.0"
5264− }
5265− },
5266− "locate-path": {
5267− "version": "3.0.0",
5268− "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
5269− "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
5270− "dev": true,
5271− "requires": {
5272− "p-locate": "^3.0.0",
5273− "path-exists": "^3.0.0"
5274− }
5275− },
5276− "p-limit": {
5277− "version": "2.0.0",
5278− "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz",
5279− "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==",
5280− "dev": true,
5281− "requires": {
5282− "p-try": "^2.0.0"
5283− }
5284− },
5285− "p-locate": {
5286− "version": "3.0.0",
5287− "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
5288− "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
5289− "dev": true,
5290− "requires": {
5291− "p-limit": "^2.0.0"
5292− }
5293− },
5294− "p-try": {
5295− "version": "2.0.0",
5296− "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
5297− "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
5298− "dev": true
5299− }
5300− }
5301− },
5302− "yargs-parser": {
5303− "version": "10.1.0",
5304− "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
5305− "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
5306− "dev": true,
5307− "requires": {
5308− "camelcase": "^4.1.0"
5309− }
5310− }
5311− }
5312−}
example/js/package.jsondeleted−35
@@ -1,35 +0,0 @@
1−{
2− "name": "create-wasm-app",
3− "version": "0.1.0",
4− "description": "create an app to consume rust-generated wasm packages",
5− "main": "index.js",
6− "bin": {
7− "create-wasm-app": ".bin/create-wasm-app.js"
8− },
9− "scripts": {
10− "build": "webpack --config webpack.config.js",
11− "start": "webpack-dev-server"
12− },
13− "repository": {
14− "type": "git",
15− "url": "git+https://github.com/rustwasm/create-wasm-app.git"
16− },
17− "keywords": [
18− "webassembly",
19− "wasm",
20− "rust",
21− "webpack"
22− ],
23− "author": "Ashley Williams <ashley666ashley@gmail.com>",
24− "license": "(MIT OR Apache-2.0)",
25− "bugs": {
26− "url": "https://github.com/rustwasm/create-wasm-app/issues"
27− },
28− "homepage": "https://github.com/rustwasm/create-wasm-app#readme",
29− "devDependencies": {
30− "webpack": "^4.16.3",
31− "webpack-cli": "^3.1.0",
32− "webpack-dev-server": "^3.1.5",
33− "copy-webpack-plugin": "^4.5.2"
34− }
35−}
example/js/webpack.config.jsdeleted−14
@@ -1,14 +0,0 @@
1−const CopyWebpackPlugin = require("copy-webpack-plugin");
2−const path = require('path');
3−
4−module.exports = {
5− entry: "./bootstrap.js",
6− output: {
7− path: path.resolve(__dirname, "dist"),
8− filename: "bootstrap.js",
9− },
10− mode: "development",
11− plugins: [
12− new CopyWebpackPlugin(['index.html'])
13− ],
14−};
example/rust/.gitignoredeleted−6
@@ -1,6 +0,0 @@
1−/target
2−**/*.rs.bk
3−Cargo.lock
4−/pkg
5−/bin
6−wasm-pack.log
example/rust/README.mddeleted−54
@@ -1,54 +0,0 @@
1−<meta charset="utf-8"/>
2−
3−# 🦀🕸️ `wasm-pack-template`
4−
5−A template for kick starting a Rust and WebAssembly project using
6−[`wasm-pack`](https://github.com/rustwasm/wasm-pack).
7−
8−This template is designed for compiling Rust libraries into WebAssembly and
9−publishing the resulting package to NPM.
10−
11−* Want to use the published NPM package in a Website? [Check out
12− `create-wasm-app`.](https://github.com/rustwasm/create-wasm-app)
13−* Want to make a monorepo-style Website without publishing to NPM? Check out
14− [`rust-webpack-template`](https://github.com/rustwasm/rust-webpack-template)
15− and/or
16− [`rust-parcel-template`](https://github.com/rustwasm/rust-parcel-template).
17−
18−## 🔋 Batteries Included
19−
20−* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating
21− between WebAssembly and JavaScript.
22−* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook)
23− for logging panic messages to the developer console.
24−* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized
25− for small code size.
26−
27−## 🚴 Usage
28−
29−### 🐑 Use `cargo generate` to Clone this Template
30−
31−[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate)
32−
33−```
34−cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
35−cd my-project
36−```
37−
38−### 🛠️ Build with `wasm-pack build`
39−
40−```
41−wasm-pack build
42−```
43−
44−### 🔬 Test in Headless Browsers with `wasm-pack test`
45−
46−```
47−wasm-pack test --headless --firefox
48−```
49−
50−### 🎁 Publish to NPM with `wasm-pack publish`
51−
52−```
53−wasm-pack publish
54−```
example/rust/src/lib.rsdeleted−45
@@ -1,45 +0,0 @@
1−use bumpalo::Bump;
2−use dodrio::{Node, Render};
3−use wasm_bindgen::prelude::*;
4−
5−struct HelloWorld<'a>(&'a str);
6−
7−impl<'who> HelloWorld<'who> {
8− fn new(who: &str) -> HelloWorld {
9− HelloWorld(who)
10− }
11−}
12−
13−impl<'who> Render for HelloWorld<'who> {
14− fn render<'a, 'bump>(&'a self, bump: &'bump Bump<'bump>) -> dodrio::node::NodeRef<'bump>
15− where
16− 'a: 'bump,
17− {
18− bump.alloc(Node::element(
19− "p",
20− [],
21− [
22− bump.alloc(Node::text("Hello, ")).into(),
23− bump.alloc(Node::text(self.0)).into(),
24− bump.alloc(Node::text("!")).into(),
25− ],
26− ))
27− .into()
28− }
29−}
30−
31−#[wasm_bindgen]
32−pub fn run() {
33− console_error_panic_hook::set_once();
34−
35− let window = web_sys::window().unwrap();
36− let document = window.document().unwrap();
37− let body = document.body().unwrap();
38−
39− // Create a new dodrio vdom contained in the body, with an initial virtual
40− // dom.
41− let mut vdom = dodrio::Vdom::new(body.as_ref(), HelloWorld::new("World"));
42−
43− // Render a new node tree into the virtual dom.
44− vdom.render(HelloWorld::new("Dodrio"));
45−}
example/rust/tests/web.rsdeleted−13
@@ -1,13 +0,0 @@
1−//! Test suite for the Web and headless browsers.
2−
3−#![cfg(target_arch = "wasm32")]
4−
5−extern crate wasm_bindgen_test;
6−use wasm_bindgen_test::*;
7−
8−wasm_bindgen_test_configure!(run_in_browser);
9−
10−#[wasm_bindgen_test]
11−fn pass() {
12− assert_eq!(1 + 1, 2);
13−}
examples/counter/Cargo.tomlnew file+33
@@ -0,0 +1,33 @@
1+[package]
2+name = "dodrio-counter"
3+version = "0.1.0"
4+authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
5+edition = "2018"
6+
7+[lib]
8+crate-type = ["cdylib"]
9+
10+[features]
11+
12+[dependencies]
13+console_error_panic_hook = "0.1.6"
14+console_log = "0.2.0"
15+dodrio = { path = "../.." }
16+log = "0.4.11"
17+wasm-bindgen = "0.2.55"
18+
19+[dependencies.web-sys]
20+version = "0.3.32"
21+features = [
22+ "console",
23+ "Document",
24+ "Event",
25+ "EventTarget",
26+ "HtmlElement",
27+ "MouseEvent",
28+ "Node",
29+ "Window",
30+]
31+
32+[dev-dependencies]
33+wasm-bindgen-test = "0.3.5"
examples/counter/README.mdnew file+21
@@ -0,0 +1,21 @@
1+# Counter
2+
3+A counter that can be incremented and decremented.
4+
5+## Source
6+
7+See `src/lib.rs`.
8+
9+## Build
10+
11+```
12+wasm-pack build --target web
13+```
14+
15+## Serve
16+
17+Use any HTTP server, for example:
18+
19+```
20+python -m SimpleHTTPServer
21+```
examples/counter/index.htmlnew file+13
@@ -0,0 +1,13 @@
1+<!DOCTYPE html>
2+<html>
3+ <head>
4+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5+ <title>Counter</title>
6+ </head>
7+ <body>
8+ <script type="module">
9+ import init from "./pkg/dodrio_counter.js";
10+ init("./pkg/dodrio_counter_bg.wasm");
11+ </script>
12+ </body>
13+</html>
examples/counter/src/lib.rsnew file+87
@@ -0,0 +1,87 @@
1+use dodrio::{bumpalo, Node, Render, RenderContext};
2+use log::*;
3+use wasm_bindgen::prelude::*;
4+
5+/// A counter that can be incremented and decremented!
6+struct Counter {
7+ count: isize,
8+}
9+
10+impl Counter {
11+ /// Construct a new, zeroed counter.
12+ fn new() -> Counter {
13+ Counter { count: 0 }
14+ }
15+
16+ /// Increment this counter's count.
17+ fn increment(&mut self) {
18+ self.count += 1;
19+ }
20+
21+ /// Decrement this counter's count.
22+ fn decrement(&mut self) {
23+ self.count -= 1;
24+ }
25+}
26+
27+// The `Render` implementation for `Counter`s displays the current count and has
28+// buttons to increment and decrement the count.
29+impl<'a> Render<'a> for Counter {
30+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
31+ use dodrio::builder::*;
32+
33+ // Stringify the count as a bump-allocated string.
34+ let count = bumpalo::format!(in cx.bump, "{}", self.count);
35+
36+ div(&cx)
37+ .children([
38+ button(&cx)
39+ .on("click", |root, vdom, _event| {
40+ // Cast the root render component to a `Counter`, since
41+ // we know that's what it is.
42+ let counter = root.unwrap_mut::<Counter>();
43+
44+ // Increment the counter.
45+ counter.increment();
46+
47+ // Since the count has updated, we should re-render the
48+ // counter on the next animation frame.
49+ vdom.schedule_render();
50+ })
51+ .children([text("+")])
52+ .finish(),
53+ text(count.into_bump_str()),
54+ button(&cx)
55+ .on("click", |root, vdom, _event| {
56+ // Same as above, but decrementing instead of incrementing.
57+ let counter = root.unwrap_mut::<Counter>();
58+ counter.decrement();
59+ vdom.schedule_render();
60+ })
61+ .children([text("-")])
62+ .finish(),
63+ ])
64+ .finish()
65+ }
66+}
67+
68+#[wasm_bindgen(start)]
69+pub fn run() {
70+ // Initialize debug logging for if/when things go wrong.
71+ console_error_panic_hook::set_once();
72+ console_log::init_with_level(Level::Trace).expect("should initialize logging OK");
73+
74+ // Get the document's `<body>`.
75+ let window = web_sys::window().unwrap();
76+ let document = window.document().unwrap();
77+ let body = document.body().unwrap();
78+
79+ // Construct a new counter component.
80+ let counter = Counter::new();
81+
82+ // Mount our counter component to the `<body>`.
83+ let vdom = dodrio::Vdom::new(&body, counter);
84+
85+ // Run the virtual DOM and its listeners forever.
86+ vdom.forget();
87+}
examples/game-of-life/Cargo.tomlnew file+29
@@ -0,0 +1,29 @@
1+[package]
2+name = "dodrio-game-of-life"
3+version = "0.1.0"
4+authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
5+edition = "2018"
6+
7+[lib]
8+crate-type = ["cdylib"]
9+
10+[features]
11+
12+[dependencies]
13+dodrio = { path = "../.." }
14+wasm-bindgen = "0.2.55"
15+console_error_panic_hook = "0.1.6"
16+js-sys = "0.3.32"
17+wasm-bindgen-futures = "0.4.5"
18+
19+[dependencies.web-sys]
20+version = "0.3.32"
21+features = [
22+ "Document",
23+ "HtmlElement",
24+ "Node",
25+ "Window",
26+]
27+
28+[dev-dependencies]
29+wasm-bindgen-test = "0.3.5"
examples/game-of-life/README.mdnew file+23
@@ -0,0 +1,23 @@
1+# Conway's Game of Life
2+
3+A port of the [Rust and WebAssembly tutorial's Game of Life
4+implementation](https://rustwasm.github.io/book/game-of-life/introduction.html)
5+to rendering to HTML with Dodrio instead of to a canvas via JavaScript.
6+
7+## Source
8+
9+See `src/lib.rs`.
10+
11+## Build
12+
13+```
14+wasm-pack build --target web
15+```
16+
17+## Serve
18+
19+Use any HTTP server, for example:
20+
21+```
22+python -m SimpleHTTPServer
23+```
examples/game-of-life/index.htmlnew file+44
@@ -0,0 +1,44 @@
1+<!DOCTYPE html>
2+<html>
3+ <head>
4+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5+ <title>Conway's Game of Life</title>
6+ <style>
7+ .cell {
8+ display: inline-block;
9+ width: 1vh;
10+ height: 1vh;
11+ border: 1px #cccccc solid;
12+ margin: -1px;
13+ padding: 0;
14+ }
15+
16+ .row {
17+ height: 1vh;
18+ margin: 0;
19+ padding: 0;
20+ }
21+
22+ body {
23+ width: 100vw;
24+ height: 100vh;
25+ display: flex;
26+ align-items: center;
27+ justify-content: center;
28+ padding: 0;
29+ margin: 0;
30+ }
31+
32+ html {
33+ padding: 0;
34+ margin: 0;
35+ }
36+ </style>
37+ </head>
38+ <body>
39+ <script type="module">
40+ import init from "./pkg/dodrio_game_of_life.js";
41+ init("./pkg/dodrio_game_of_life_bg.wasm");
42+ </script>
43+ </body>
44+</html>
examples/game-of-life/src/lib.rsnew file+218
@@ -0,0 +1,218 @@
1+use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
2+use std::cell::RefCell;
3+use std::rc::Rc;
4+use wasm_bindgen::{prelude::*, JsCast};
5+
6+#[repr(u8)]
7+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8+pub enum Cell {
9+ Dead = 0,
10+ Alive = 1,
11+}
12+
13+/// The universe containing all of the cells for our Game of Life.
14+struct Universe {
15+ width: u32,
16+ height: u32,
17+ cells: Vec<Cell>,
18+}
19+
20+impl Universe {
21+ /// Construct a new universe.
22+ pub fn new() -> Universe {
23+ let width = 64;
24+ let height = 64;
25+
26+ let cells = (0..width * height)
27+ .map(|i| {
28+ if i % 2 == 0 || i % 7 == 0 {
29+ Cell::Alive
30+ } else {
31+ Cell::Dead
32+ }
33+ })
34+ .collect();
35+
36+ Universe {
37+ width,
38+ height,
39+ cells,
40+ }
41+ }
42+
43+ /// Compute one tick of the universe.
44+ pub fn tick(&mut self) {
45+ let mut next = self.cells.clone();
46+
47+ for row in 0..self.height {
48+ for col in 0..self.width {
49+ let idx = self.get_index(row, col);
50+ let cell = self.cells[idx];
51+ let live_neighbors = self.live_neighbor_count(row, col);
52+
53+ let next_cell = match (cell, live_neighbors) {
54+ // Rule 1: Any live cell with fewer than two live neighbours
55+ // dies, as if caused by underpopulation.
56+ (Cell::Alive, x) if x < 2 => Cell::Dead,
57+ // Rule 2: Any live cell with two or three live neighbours
58+ // lives on to the next generation.
59+ (Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
60+ // Rule 3: Any live cell with more than three live
61+ // neighbours dies, as if by overpopulation.
62+ (Cell::Alive, x) if x > 3 => Cell::Dead,
63+ // Rule 4: Any dead cell with exactly three live neighbours
64+ // becomes a live cell, as if by reproduction.
65+ (Cell::Dead, 3) => Cell::Alive,
66+ // All other cells remain in the same state.
67+ (otherwise, _) => otherwise,
68+ };
69+
70+ next[idx] = next_cell;
71+ }
72+ }
73+
74+ self.cells = next;
75+ }
76+
77+ fn get_index(&self, row: u32, column: u32) -> usize {
78+ (row * self.width + column) as usize
79+ }
80+
81+ fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
82+ let mut count = 0;
83+
84+ let north = if row == 0 { self.height - 1 } else { row - 1 };
85+
86+ let south = if row == self.height - 1 { 0 } else { row + 1 };
87+
88+ let west = if column == 0 {
89+ self.width - 1
90+ } else {
91+ column - 1
92+ };
93+
94+ let east = if column == self.width - 1 {
95+ 0
96+ } else {
97+ column + 1
98+ };
99+
100+ let nw = self.get_index(north, west);
101+ count += self.cells[nw] as u8;
102+
103+ let n = self.get_index(north, column);
104+ count += self.cells[n] as u8;
105+
106+ let ne = self.get_index(north, east);
107+ count += self.cells[ne] as u8;
108+
109+ let w = self.get_index(row, west);
110+ count += self.cells[w] as u8;
111+
112+ let e = self.get_index(row, east);
113+ count += self.cells[e] as u8;
114+
115+ let sw = self.get_index(south, west);
116+ count += self.cells[sw] as u8;
117+
118+ let s = self.get_index(south, column);
119+ count += self.cells[s] as u8;
120+
121+ let se = self.get_index(south, east);
122+ count += self.cells[se] as u8;
123+
124+ count
125+ }
126+}
127+
128+/// The rendering implementation for our Game of Life.
129+impl<'a> Render<'a> for Universe {
130+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
131+ use dodrio::builder::*;
132+
133+ let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, cx.bump);
134+
135+ for row in self.cells.chunks(self.width as usize) {
136+ let mut cells =
137+ bumpalo::collections::Vec::with_capacity_in(self.width as usize, cx.bump);
138+
139+ for cell in row {
140+ cells.push(
141+ span(&cx)
142+ .attr("class", "cell")
143+ .attr(
144+ "style",
145+ match cell {
146+ Cell::Alive => "background-color: black",
147+ Cell::Dead => "background-color: white",
148+ },
149+ )
150+ .finish(),
151+ );
152+ }
153+
154+ rows.push(div(&cx).attr("class", "row").children(cells).finish());
155+ }
156+
157+ div(&cx).attr("class", "universe").children(rows).finish()
158+ }
159+}
160+
161+#[wasm_bindgen(start)]
162+pub fn run() {
163+ // Set up the panic hook for debugging when things go wrong.
164+ console_error_panic_hook::set_once();
165+
166+ // Grab the document's `<body>`.
167+ let window = web_sys::window().unwrap_throw();
168+ let document = window.document().unwrap_throw();
169+ let body = document.body().unwrap_throw();
170+
171+ // Create a new Game of Life `Universe` render component.
172+ let universe = Universe::new();
173+
174+ // Create a virtual DOM and mount it and the `Universe` render component to the
175+ // `<body>`.
176+ let vdom = Vdom::new(body.as_ref(), universe);
177+
178+ // Kick off a loop that keeps computing a tick of the universe and then
179+ // re-rendering on every animation frame.
180+ let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
181+ let rc2 = rc.clone();
182+ let window2 = window.clone();
183+ let weak = vdom.weak();
184+ let f = Closure::wrap(Box::new(move || {
185+ weak.schedule_render();
186+
187+ wasm_bindgen_futures::spawn_local({
188+ let weak = weak.clone();
189+ async move {
190+ let fut = weak.with_component(|root| {
191+ let universe = root.unwrap_mut::<Universe>();
192+ universe.tick();
193+ });
194+
195+ if fut.await.is_err() {
196+ wasm_bindgen::throw_str("impossible, we always keep the vdom alive");
197+ }
198+ }
199+ });
200+
201+ window
202+ .request_animation_frame(
203+ rc.borrow()
204+ .as_ref()
205+ .unwrap_throw()
206+ .as_ref()
207+ .unchecked_ref::<js_sys::Function>(),
208+ )
209+ .unwrap_throw();
210+ }) as Box<dyn FnMut()>);
211+ window2
212+ .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
213+ .unwrap_throw();
214+ *rc2.borrow_mut() = Some(f);
215+
216+ // Run the virtual DOM forever and don't unmount it.
217+ vdom.forget();
218+}
example/rust/Cargo.toml → examples/hello-world/Cargo.tomlrenamed+6−7
@@ -1,22 +1,21 @@
11[package]
2−name = "dodrio-example"
2+name = "dodrio-hello-world"
33version = "0.1.0"
44authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
55edition = "2018"
66
77[lib]
8−crate-type = ["cdylib", "rlib"]
8+crate-type = ["cdylib"]
99
1010[features]
1111
1212[dependencies]
13−bumpalo = { path = "../../../bumpalo" }
1413dodrio = { path = "../.." }
15−wasm-bindgen = "0.2"
16−console_error_panic_hook = "0.1.1"
14+wasm-bindgen = "0.2.55"
15+console_error_panic_hook = "0.1.6"
1716
1817[dependencies.web-sys]
19−version = "0.3.2"
18+version = "0.3.32"
2019features = [
2120 "Document",
2221 "HtmlElement",
@@ -25,4 +24,4 @@ features = [
2524]
2625
2726[dev-dependencies]
28−wasm-bindgen-test = "0.2"
27+wasm-bindgen-test = "0.3.5"
examples/hello-world/README.mdnew file+21
@@ -0,0 +1,21 @@
1+# Hello World
2+
3+The most basic example!
4+
5+## Source
6+
7+See `src/lib.rs`.
8+
9+## Build
10+
11+```
12+wasm-pack build --target web
13+```
14+
15+## Serve
16+
17+Use any HTTP server, for example:
18+
19+```
20+python -m SimpleHTTPServer
21+```
examples/hello-world/index.htmlnew file+13
@@ -0,0 +1,13 @@
1+<!DOCTYPE html>
2+<html>
3+ <head>
4+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5+ <title>Hello, World!</title>
6+ </head>
7+ <body>
8+ <script type="module">
9+ import init from "./pkg/dodrio_hello_world.js";
10+ init("./pkg/dodrio_hello_world_bg.wasm");
11+ </script>
12+ </body>
13+</html>
examples/hello-world/src/lib.rsnew file+42
@@ -0,0 +1,42 @@
1+use dodrio::{builder::*, bumpalo};
2+use dodrio::{Node, Render, RenderContext, Vdom};
3+use wasm_bindgen::prelude::*;
4+
5+/// A rendering component that displays a greeting.
6+struct Hello {
7+ /// Who to greet.
8+ who: String,
9+}
10+
11+// The `Render` implementation describes how to render a `Hello` component into
12+// HTML.
13+impl<'a> Render<'a> for Hello {
14+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
15+ let msg = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
16+ let msg = msg.into_bump_str();
17+ p(&cx).children([text(msg)]).finish()
18+ }
19+}
20+
21+#[wasm_bindgen(start)]
22+pub fn run() {
23+ // Set up the panic hook for debugging when things go wrong.
24+ console_error_panic_hook::set_once();
25+
26+ // Grab the document's `<body>`.
27+ let window = web_sys::window().unwrap_throw();
28+ let document = window.document().unwrap_throw();
29+ let body = document.body().unwrap_throw();
30+
31+ // Create a new `Hello` render component.
32+ let component = Hello {
33+ who: String::from("World"),
34+ };
35+
36+ // Create a virtual DOM and mount it and the `Hello` render component to the
37+ // `<body>`.
38+ let vdom = Vdom::new(body.as_ref(), component);
39+
40+ // Run the virtual DOM forever and don't unmount it.
41+ vdom.forget();
42+}
examples/input-form/Cargo.tomlnew file+32
@@ -0,0 +1,32 @@
1+[package]
2+name = "dodrio-input-form"
3+version = "0.1.0"
4+authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
5+edition = "2018"
6+
7+[lib]
8+crate-type = ["cdylib"]
9+
10+[features]
11+
12+[dependencies]
13+dodrio = { path = "../.." }
14+wasm-bindgen = "0.2.55"
15+console_error_panic_hook = "0.1.6"
16+
17+[dependencies.web-sys]
18+version = "0.3.32"
19+features = [
20+ "console",
21+ "Document",
22+ "Event",
23+ "EventTarget",
24+ "HtmlElement",
25+ "HtmlInputElement",
26+ "MouseEvent",
27+ "Node",
28+ "Window",
29+]
30+
31+[dev-dependencies]
32+wasm-bindgen-test = "0.3.5"
examples/input-form/README.mdnew file+22
@@ -0,0 +1,22 @@
1+# Input Forms
2+
3+Renders a greeting to a user-supplied person via an `<input>` value. The
4+greeting is live-updated with new inputs.
5+
6+## Source
7+
8+See `src/lib.rs`.
9+
10+## Build
11+
12+```
13+wasm-pack build --target web
14+```
15+
16+## Serve
17+
18+Use any HTTP server, for example:
19+
20+```
21+python -m SimpleHTTPServer
22+```
examples/input-form/index.htmlnew file+13
@@ -0,0 +1,13 @@
1+<!DOCTYPE html>
2+<html>
3+ <head>
4+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5+ <title>Input Form</title>
6+ </head>
7+ <body>
8+ <script type="module">
9+ import init from "./pkg/dodrio_input_form.js";
10+ init("./pkg/dodrio_input_form_bg.wasm");
11+ </script>
12+ </body>
13+</html>
examples/input-form/src/lib.rsnew file+81
@@ -0,0 +1,81 @@
1+use dodrio::{bumpalo, Node, Render, RenderContext};
2+use wasm_bindgen::prelude::*;
3+use wasm_bindgen::JsCast;
4+
5+/// Say hello to someone.
6+struct SayHelloTo {
7+ /// Who to say hello to.
8+ who: String,
9+}
10+
11+impl SayHelloTo {
12+ /// Construct a new `SayHelloTo` component.
13+ fn new<S: Into<String>>(who: S) -> SayHelloTo {
14+ let who = who.into();
15+ SayHelloTo { who }
16+ }
17+
18+ /// Update who to say hello to.
19+ fn set_who(&mut self, who: String) {
20+ self.who = who;
21+ }
22+}
23+
24+// The `Render` implementation has a text `<input>` and a `<div>` that shows a
25+// greeting to the `<input>`'s value.
26+impl<'a> Render<'a> for SayHelloTo {
27+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
28+ use dodrio::builder::*;
29+
30+ div(&cx)
31+ .children([
32+ input(&cx)
33+ .attr("type", "text")
34+ .attr(
35+ "value",
36+ bumpalo::format!(in cx.bump, "{}", self.who).into_bump_str(),
37+ )
38+ .on("input", |root, vdom, event| {
39+ // If the event's target is our input...
40+ let input = match event
41+ .target()
42+ .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
43+ {
44+ None => return,
45+ Some(input) => input,
46+ };
47+
48+ // ...then get its value and update who we are greeting.
49+ let value = input.value();
50+ let hello = root.unwrap_mut::<SayHelloTo>();
51+ hello.set_who(value);
52+
53+ // Finally, re-render the component on the next animation frame.
54+ vdom.schedule_render();
55+ })
56+ .finish(),
57+ text(bumpalo::format!(in cx.bump, "Hello, {}!", self.who).into_bump_str()),
58+ ])
59+ .finish()
60+ }
61+}
62+
63+#[wasm_bindgen(start)]
64+pub fn run() {
65+ // Initialize debugging for when/if something goes wrong.
66+ console_error_panic_hook::set_once();
67+
68+ // Get the document's `<body>`.
69+ let window = web_sys::window().unwrap();
70+ let document = window.document().unwrap();
71+ let body = document.body().unwrap();
72+
73+ // Construct a new `SayHelloTo` rendering component.
74+ let say_hello = SayHelloTo::new("World");
75+
76+ // Mount the component to the `<body>`.
77+ let vdom = dodrio::Vdom::new(&body, say_hello);
78+
79+ // Run the component forever.
80+ vdom.forget();
81+}
examples/js-component/Cargo.tomlnew file+26
@@ -0,0 +1,26 @@
1+[package]
2+name = "dodrio-js-component"
3+version = "0.1.0"
4+authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
5+edition = "2018"
6+
7+[lib]
8+crate-type = ["cdylib"]
9+
10+[features]
11+
12+[dependencies]
13+dodrio = { path = "../.." }
14+dodrio-js-api = { path = "../../crates/js-api" }
15+wasm-bindgen = "0.2.55"
16+console_error_panic_hook = "0.1.6"
17+js-sys = "0.3.32"
18+
19+[dependencies.web-sys]
20+version = "0.3.32"
21+features = [
22+ "Document",
23+ "HtmlElement",
24+ "Node",
25+ "Window",
26+]
examples/js-component/README.mdnew file+23
@@ -0,0 +1,23 @@
1+# Defining a Rendering Component in JavaScript
2+
3+Heterogeneous Rust and JavaScript rendering component trees!
4+
5+## Source
6+
7+See `greeting.js` for the JavaScript code.
8+
9+See `src/lib.rs` for the Rust code.
10+
11+## Build
12+
13+```
14+wasm-pack build --target web
15+```
16+
17+## Serve
18+
19+Use any HTTP server, for example:
20+
21+```
22+python -m SimpleHTTPServer
23+```
examples/js-component/greeting.jsnew file+41
@@ -0,0 +1,41 @@
1+// A rendering component defined in JavaScript!
2+class Greeting {
3+ constructor(who) {
4+ this.who = who;
5+ }
6+
7+ render() {
8+ return {
9+ tagName: "p",
10+ attributes: [
11+ {
12+ name: "class",
13+ value: "greeting",
14+ },
15+ ],
16+ listeners: [
17+ {
18+ on: "click",
19+ callback: this.onClick.bind(this),
20+ }
21+ ],
22+ children: [
23+ "Hello, ",
24+ {
25+ tagName: "strong",
26+ children: [this.who],
27+ }
28+ ],
29+ };
30+ }
31+
32+ async onClick(vdom, event) {
33+ // Be more excited!
34+ this.who += "!";
35+
36+ // Schedule a re-render.
37+ await vdom.render();
38+
39+ console.log("re-rendering finished!");
40+ }
41+}
examples/js-component/index.htmlnew file+14
@@ -0,0 +1,14 @@
1+<!DOCTYPE html>
2+<html>
3+ <head>
4+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5+ <title>A JavaScript component!</title>
6+ </head>
7+ <body>
8+ <script src="greeting.js"></script>
9+ <script type="module">
10+ import init from "./pkg/dodrio_js_component.js";
11+ init("./pkg/dodrio_js_component_bg.wasm");
12+ </script>
13+ </body>
14+</html>
examples/js-component/src/lib.rsnew file+63
@@ -0,0 +1,63 @@
1+use dodrio::{Node, Render, RenderContext, Vdom};
2+use dodrio_js_api::JsRender;
3+use js_sys::Object;
4+use wasm_bindgen::prelude::*;
5+
6+#[wasm_bindgen]
7+extern "C" {
8+ // Import the JS `Greeting` class.
9+ #[wasm_bindgen(extends = Object)]
10+ #[derive(Clone, Debug)]
11+ type Greeting;
12+
13+ // And the `Greeting` class's constructor.
14+ #[wasm_bindgen(constructor)]
15+ fn new(who: &str) -> Greeting;
16+}
17+
18+/// This is our Rust rendering component that wraps the JS rendering component.
19+pub struct GreetingViaJs {
20+ js: JsRender,
21+}
22+
23+impl GreetingViaJs {
24+ /// Create a new `GreetingViaJs`, which will internally create a new JS
25+ /// `Greeting`.
26+ pub fn new(who: &str) -> GreetingViaJs {
27+ let js = JsRender::new(Greeting::new(who));
28+ GreetingViaJs { js }
29+ }
30+}
31+
32+/// Here's the `Render` implementation! This adds a `<p>` element and some text
33+/// around whatever the inner JS `Greeting` component renders.
34+impl<'a> Render<'a> for GreetingViaJs {
35+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
36+ use dodrio::builder::*;
37+ p(&cx)
38+ .children([text("JavaScript says: "), self.js.render(cx)])
39+ .finish()
40+ }
41+}
42+
43+/// Finally our main initialization function that kicks everything off!
44+#[wasm_bindgen(start)]
45+pub fn run() {
46+ // Set up the panic hook for debugging when things go wrong.
47+ console_error_panic_hook::set_once();
48+
49+ // Grab the document's `<body>`.
50+ let window = web_sys::window().unwrap_throw();
51+ let document = window.document().unwrap_throw();
52+ let body = document.body().unwrap_throw();
53+
54+ // Create a new `GreetingViaJs` render component.
55+ let component = GreetingViaJs::new("World");
56+
57+ // Create a virtual DOM and mount it and the `GreetingViaJs` render component to the
58+ // `<body>`.
59+ let vdom = Vdom::new(body.as_ref(), component);
60+
61+ // Run the virtual DOM forever and don't unmount it.
62+ vdom.forget();
63+}
examples/moire/Cargo.tomlnew file+43
@@ -0,0 +1,43 @@
1+[package]
2+name = "dodrio-moire"
3+version = "0.1.0"
4+authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
5+edition = "2018"
6+
7+[lib]
8+crate-type = ["cdylib"]
9+
10+[dependencies]
11+cfg-if = "0.1.10"
12+dodrio = { path = "../.." }
13+log = "0.4.11"
14+wasm-bindgen = "0.2.55"
15+wasm-bindgen-futures = "0.4.5"
16+
17+# Optional dependencies for logging.
18+console_error_panic_hook = { optional = true, version = "0.1.6" }
19+console_log = { optional = true, version = "0.2.0" }
20+js-sys = "0.3.32"
21+
22+[dependencies.web-sys]
23+version = "0.3.32"
24+features = [
25+ "Document",
26+ "Element",
27+ "Event",
28+ "EventTarget",
29+ "HtmlElement",
30+ "HtmlInputElement",
31+ "Node",
32+ "Performance",
33+ "Window",
34+]
35+
36+[dev-dependencies]
37+wasm-bindgen-test = "0.3.5"
38+
39+[features]
40+logging = [
41+ "console_log",
42+ "console_error_panic_hook",
43+]
examples/moire/README.mdnew file+26
@@ -0,0 +1,26 @@
1+# Moiré Patterns!
2+
3+This was originally a demo meant to show off [WebRender][], and so the
4+performance bottleneck is painting, not DOM manipulation, but it looks cool
5+either way!
6+
7+[WebRender]: https://hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank/
8+
9+## Source
10+
11+See `src/lib.rs` for the main bits, and `src/colors.rs` for HSV to RGB color
12+conversion and other color utilities.
13+
14+## Build
15+
16+```
17+wasm-pack build --target web
18+```
19+
20+## Serve
21+
22+Use any HTTP server, for example:
23+
24+```
25+python -m SimpleHTTPServer
26+```
examples/moire/index.htmlnew file+67
@@ -0,0 +1,67 @@
1+<!DOCTYPE html>
2+<html>
3+ <head>
4+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5+ <title>Moire patterns! Originally a WebRender demo</title>
6+ <style>
7+ main {
8+ position: absolute;
9+ top: 0;
10+ left: 0;
11+ right: 0;
12+ bottom: 0;
13+ overflow: hidden;
14+ }
15+ .circle {
16+ border-radius: 50%;
17+ position: absolute;
18+ top: 50%;
19+ left: 50%;
20+ border-style: solid;
21+ border-width: 3px;
22+ }
23+ .object {
24+ position: absolute;
25+ width: 100%;
26+ height: 100%;
27+ left: 0;
28+ top: 0;
29+ }
30+ code {
31+ position: relative;
32+ font-size: 150%;
33+ z-index: 10;
34+ }
35+ #circle-count {
36+ position: absolute;
37+ margin-top: 30px;
38+ z-index: 2;
39+ }
40+ </style>
41+ </head>
42+ <body>
43+ <code>FPS: <output id="fps"></code></code>
44+ <div id="scene"></div>
45+ <script type="module">
46+ import init from "./pkg/dodrio_moire.js";
47+ init("./pkg/dodrio_moire_bg.wasm");
48+ </script>
49+ <script>
50+ let lastTime = 0
51+ let frameCount = 0
52+ const fps = document.getElementById('fps')
53+ function updateFPS(time) {
54+ frameCount++
55+ if (lastTime + 1000.0 <= time) {
56+ fps.textContent = frameCount
57+ lastTime = time
58+ frameCount = 0
59+ }
60+ }
61+ requestAnimationFrame(function loop(now) {
62+ updateFPS(now);
63+ requestAnimationFrame(loop);
64+ })
65+ </script>
66+ </body>
67+</html>
examples/moire/src/colors.rsnew file+162
@@ -0,0 +1,162 @@
1+//! Color utility functions.
2+
3+use std::f64;
4+
5+/// An RGB color.
6+#[derive(Copy, Clone)]
7+pub struct Rgb {
8+ pub r: u8,
9+ pub g: u8,
10+ pub b: u8,
11+}
12+
13+/// An HSV color.
14+#[derive(Copy, Clone)]
15+pub struct Hsv {
16+ pub h: f64,
17+ pub s: f64,
18+ pub v: f64,
19+}
20+
21+/// Convert an RGB color into HSV.
22+impl From<Rgb> for Hsv {
23+ fn from(Rgb { r, g, b }: Rgb) -> Hsv {
24+ let r = r as f64 / 255.0;
25+ let g = g as f64 / 255.0;
26+ let b = b as f64 / 255.0;
27+ let max = f64::max(f64::max(r, g), b);
28+ let min = f64::min(f64::min(r, g), b);
29+ let v = max;
30+ let diff = v - min;
31+ let mut h = 0.0;
32+ let mut s = 0.0;
33+ if diff != 0.0 {
34+ s = diff / v;
35+ let diffc = |c| (v - c) / 6.0 / diff + 1.0 / 2.0;
36+ let rr = diffc(r);
37+ let gg = diffc(g);
38+ let bb = diffc(b);
39+ if r == v {
40+ h = bb - gg;
41+ } else if g == v {
42+ h = (1.0 / 3.0) + rr - bb;
43+ } else if b == v {
44+ h = (2.0 / 3.0) + gg - rr;
45+ }
46+ }
47+ Hsv { h, s, v }
48+ }
49+}
50+
51+/// Convert an HSV color into RGB.
52+impl From<Hsv> for Rgb {
53+ fn from(Hsv { h, s, v }: Hsv) -> Rgb {
54+ let i = (h * 6.0).floor();
55+ let f = h * 6.0 - i;
56+ let p = v * (1.0 - s);
57+ let q = v * (1.0 - f * s);
58+ let t = v * (1.0 - (1.0 - f) * s);
59+ let r;
60+ let g;
61+ let b;
62+ match i as u64 % 6 {
63+ 0 => {
64+ r = v;
65+ g = t;
66+ b = p;
67+ }
68+ 1 => {
69+ r = q;
70+ g = v;
71+ b = p;
72+ }
73+ 2 => {
74+ r = p;
75+ g = v;
76+ b = t;
77+ }
78+ 3 => {
79+ r = p;
80+ g = q;
81+ b = v;
82+ }
83+ 4 => {
84+ r = t;
85+ g = p;
86+ b = v;
87+ }
88+ 5 => {
89+ r = v;
90+ g = p;
91+ b = q;
92+ }
93+ _ => wasm_bindgen::throw_str("impossible"),
94+ }
95+ let r = (r * 255.0).round() as u8;
96+ let g = (g * 255.0).round() as u8;
97+ let b = (b * 255.0).round() as u8;
98+ Rgb { r, g, b }
99+ }
100+}
101+
102+/// Linearly interpolate between two values.
103+fn lerp(a: f64, b: f64, t: f64) -> f64 {
104+ a + (b - a) * t
105+}
106+
107+/// Clamp the `x` value to within the range `lo..hi`.
108+fn clamp(lo: f64, hi: f64, x: f64) -> f64 {
109+ if x < lo {
110+ lo
111+ } else if x > hi {
112+ hi
113+ } else {
114+ x
115+ }
116+}
117+
118+/// Linearly interpolate two HSV colors and convert the result into RGB.
119+fn lerp_hsv_to_rgb(a: Hsv, b: Hsv, t: f64) -> Rgb {
120+ let h = lerp(a.h, b.h, t);
121+ let s = lerp(a.s, b.s, t);
122+ let v = lerp(a.v, b.v, t);
123+ let hsv = Hsv { h, s, v };
124+ hsv.into()
125+}
126+
127+thread_local! {
128+ static PALETTES: [(Hsv, Hsv); 3] = [
129+ (
130+ (Rgb { r: 0xfe, g: 0xf6, b: 0xdf }).into(),
131+ (Rgb { r: 0x64, g: 0xc3, b: 0xc8 }).into(),
132+ ),
133+ (
134+ (Rgb { r: 0xfc, g: 0xcf, b: 0x74 }).into(),
135+ (Rgb { r: 0x41, g: 0x65, b: 0x7f }).into(),
136+ ),
137+ (
138+ (Rgb { r: 0xd9, g: 0xe0, b: 0xe8 }).into(),
139+ (Rgb { r: 0x94, g: 0x5d, b: 0x72 }).into(),
140+ ),
141+ ];
142+}
143+
144+/// Get a color from our color palette that is linearly interpolated based on
145+/// the `t` elapsed time value.
146+///
147+/// The `which` function allows callers to select whether they want the
148+/// foreground or background color.
149+pub fn get_interpolated_color<F>(mut which: F, t: f64) -> Rgb
150+where
151+ F: FnMut((Hsv, Hsv)) -> Hsv,
152+{
153+ PALETTES.with(|palettes| {
154+ let t = t * (palettes.len() as f64);
155+ let prev_index = t.floor() as usize % palettes.len();
156+ let next_index = (prev_index + 1) % palettes.len();
157+ let t = clamp(-0.5, 0.5, (t % 1.0 - 0.5) * 5.0) + 0.5;
158+ let a = which(palettes[prev_index]);
159+ let b = which(palettes[next_index]);
160+ lerp_hsv_to_rgb(a, b, t)
161+ })
162+}
examples/moire/src/lib.rsnew file+259
@@ -0,0 +1,259 @@
1+mod colors;
2+
3+use cfg_if::cfg_if;
4+use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
5+use std::cell::RefCell;
6+use std::rc::Rc;
7+use std::str;
8+use wasm_bindgen::{prelude::*, JsCast};
9+
10+/// This is the main function that is automatically invoked when the wasm module
11+/// is loaded.
12+#[wasm_bindgen(start)]
13+pub fn run() {
14+ // Set up the panic hook for debugging when things go wrong.
15+ init_logging();
16+
17+ // Grab the document's `<body>`.
18+ let window = web_sys::window().unwrap_throw();
19+ let document = window.document().unwrap_throw();
20+ let scene = document.get_element_by_id("scene").unwrap_throw();
21+
22+ // Create a new `Moire` render component.
23+ let component = Moire::new();
24+
25+ // Create a virtual DOM and mount it and the `Moire` render component to the
26+ // scene.
27+ let vdom = Vdom::new(&scene, component);
28+
29+ // Kick off a loop that keeps re-rendering on every animation frame.
30+ let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
31+ let rc2 = rc.clone();
32+ let window2 = window.clone();
33+ let weak = vdom.weak();
34+ let f = Closure::wrap(Box::new(move || {
35+ weak.schedule_render();
36+ window
37+ .request_animation_frame(
38+ rc.borrow()
39+ .as_ref()
40+ .unwrap_throw()
41+ .as_ref()
42+ .unchecked_ref::<js_sys::Function>(),
43+ )
44+ .unwrap_throw();
45+ }) as Box<dyn FnMut()>);
46+ window2
47+ .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
48+ .unwrap_throw();
49+ *rc2.borrow_mut() = Some(f);
50+
51+ // Run the virtual DOM forever and don't unmount it.
52+ vdom.forget();
53+}
54+
55+cfg_if! {
56+ if #[cfg(feature = "logging")] {
57+ fn init_logging() {
58+ console_error_panic_hook::set_once();
59+ console_log::init_with_level(log::Level::Trace)
60+ .expect_throw("should initialize logging OK");
61+ }
62+ } else {
63+ fn init_logging() {
64+ // Do nothing.
65+ }
66+ }
67+}
68+
69+/// A rendering component that renders two overlapping sets of concentric
70+/// circles that move around and form Moiré patterns.
71+///
72+/// https://en.wikipedia.org/wiki/Moir%C3%A9_pattern
73+pub struct Moire {
74+ // How many circles to render in each of our sets. This is controlled by the
75+ // `<input>` slider.
76+ count: u32,
77+}
78+
79+impl Moire {
80+ /// Construct a new `Moire` rendering component.
81+ ///
82+ /// Defaults to 200 circles.
83+ pub fn new() -> Moire {
84+ Moire { count: 200 }
85+ }
86+
87+ /// Callback for "change" events emitted on the `<input>` slider that
88+ /// controls how many circles we render on each frame.
89+ fn on_change(&mut self, event: web_sys::Event) {
90+ // Get the `<input>` element.
91+ let input = event
92+ .target()
93+ .unwrap_throw()
94+ .unchecked_into::<web_sys::HtmlInputElement>();
95+
96+ // Parse its new value.
97+ let value = input.value();
98+ let value: u32 = value
99+ .parse()
100+ .expect_throw("<input type=\"range\"> value should always be an integer");
101+
102+ // And update our circle count!
103+ self.count = value;
104+ }
105+}
106+
107+impl<'a> Render<'a> for Moire {
108+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
109+ use dodrio::builder::*;
110+
111+ let elapsed = web_sys::window()
112+ .unwrap_throw()
113+ .performance()
114+ .unwrap_throw()
115+ .now();
116+ let elapsed = elapsed / 1600.0;
117+
118+ main(&cx)
119+ .attr("style", self.moire_style(cx, elapsed))
120+ .children([
121+ // The `<input>` that lets users control how many circles to
122+ // render.
123+ input(&cx)
124+ .attr("id", "circle-count")
125+ .attr("type", "range")
126+ .attr("min", "30")
127+ .attr("max", "500")
128+ .attr("value", {
129+ let count = bumpalo::format!(in cx.bump, "{}", self.count);
130+ count.into_bump_str()
131+ })
132+ .on("change", |root, _vdom, event| {
133+ root.unwrap_mut::<Moire>().on_change(event);
134+
135+ // Note: there is no need to manually schedule a
136+ // re-render here, since we already started a
137+ // `requestAnimationFrame` loop that automatically
138+ // re-renders on every frame in the `run` function.
139+ })
140+ .finish(),
141+ // And the actual concentric circles that form the Moire patterns.
142+ self.orbiting_objects(cx, elapsed),
143+ self.lemniscate_objects(cx, elapsed),
144+ ])
145+ .finish()
146+ }
147+}
148+
149+/// Rendering helper methods.
150+impl Moire {
151+ /// Generate the main Moire element's inline CSS styles.
152+ fn moire_style<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> &'a str {
153+ let elapsed = elapsed / 3.0;
154+ let color = colors::get_interpolated_color(|(_, bg)| bg, elapsed % 1.0);
155+ let style = bumpalo::format!(
156+ in cx.bump,
157+ "background-color: rgb({}, {}, {})",
158+ color.r,
159+ color.g,
160+ color.b
161+ );
162+ style.into_bump_str()
163+ }
164+
165+ /// Generate the orbiting circle objects.
166+ fn orbiting_objects<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> Node<'a> {
167+ const D: f64 = 200.0;
168+ let x = elapsed.sin() * D;
169+ let y = elapsed.cos() * D;
170+ self.moving_object(cx, elapsed, x, y)
171+ }
172+
173+ /// Generate the lemniscate circle objects that interact with the orbiting
174+ /// circle objects to form Moire patterns.
175+ fn lemniscate_objects<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> Node<'a> {
176+ const A: f64 = 150.0;
177+ let x = elapsed.sin() * A;
178+ let y = elapsed.sin() * elapsed.cos() * A;
179+ self.moving_object(cx, elapsed, x, y)
180+ }
181+
182+ fn moving_object<'a>(
183+ &self,
184+ cx: &mut RenderContext<'a>,
185+ elapsed: f64,
186+ x: f64,
187+ y: f64,
188+ ) -> Node<'a> {
189+ use dodrio::builder::*;
190+ div(&cx)
191+ .attr("class", "object")
192+ .attr("style", self.moving_object_style(cx, x, y))
193+ .children([self.circle(cx, elapsed, self.count)])
194+ .finish()
195+ }
196+
197+ /// Generate inline CSS styles for a moving object.
198+ fn moving_object_style<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64) -> &'a str {
199+ bumpalo::format!(in cx.bump, "left: {}px; top: {}px;", x, y).into_bump_str()
200+ }
201+
202+ /// Recursively generate `self.count` concentric circles.
203+ fn circle<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64, n: u32) -> Node<'a> {
204+ use dodrio::builder::*;
205+
206+ let r = n * 16;
207+
208+ let mut circle = div(&cx)
209+ .attr("class", "circle")
210+ .attr("data-radius", {
211+ let r = bumpalo::format!(in cx.bump, "{}", r);
212+ r.into_bump_str()
213+ })
214+ .attr("style", self.circle_style(cx, elapsed, n, r));
215+
216+ if n > 0 {
217+ circle = circle.child(self.circle(cx, elapsed, n - 1));
218+ }
219+
220+ circle.finish()
221+ }
222+
223+ /// Generate inline CSS styles for a circle.
224+ fn circle_style<'a>(
225+ &self,
226+ cx: &mut RenderContext<'a>,
227+ elapsed: f64,
228+ n: u32,
229+ r: u32,
230+ ) -> &'a str {
231+ let (border, alpha) = self.circle_color(elapsed, n);
232+ let margin = r / 2 + 3;
233+ let style = bumpalo::format!(
234+ in cx.bump,
235+ "border-color: rgba({}, {}, {}, {});\
236+ margin-left: -{}px;\
237+ margin-top: -{}px;\
238+ width: {}px;\
239+ height: {}px;",
240+ border.r,
241+ border.g,
242+ border.b,
243+ alpha,
244+ margin,
245+ margin,
246+ r,
247+ r
248+ );
249+ style.into_bump_str()
250+ }
251+
252+ /// Compute the color for a circle.
253+ fn circle_color(&self, elapsed: f64, n: u32) -> (colors::Rgb, f64) {
254+ let elapsed = elapsed / 3.0;
255+ let base = colors::get_interpolated_color(|(fg, _)| fg, elapsed % 1.0);
256+ let lightness = 1.0 - (n as f64) / (self.count as f64);
257+ (base, lightness)
258+ }
259+}
examples/sierpinski-triangle/Cargo.tomlnew file+28
@@ -0,0 +1,28 @@
1+[package]
2+name = "dodrio-sierpinski"
3+version = "0.1.0"
4+authors = ["Steven Seguin <steven.seguin@gmail.com>"]
5+edition = "2018"
6+
7+[lib]
8+crate-type = ["cdylib"]
9+
10+[dependencies]
11+dodrio = { path = "../.." }
12+wasm-bindgen = "0.2.55"
13+console_error_panic_hook = "0.1.6"
14+js-sys = "0.3.32"
15+wasm-bindgen-futures = "0.4.5"
16+
17+[dependencies.web-sys]
18+version = "0.3.32"
19+features = [
20+ "Document",
21+ "HtmlElement",
22+ "Node",
23+ "Performance",
24+ "Window",
25+]
26+
27+[dev-dependencies]
28+wasm-bindgen-test = "0.3.5"
examples/sierpinski-triangle/README.mdnew file+24
@@ -0,0 +1,24 @@
1+# Sierpinski Triangle
2+
3+A Sierpinkski triangle that constantly shrinks and grows, and whose nodes have
4+a value that increments by one every second.
5+
6+Ported from the React Fiber example [here](https://github.com/claudiopro/react-fiber-vs-stack-demo).
7+
8+## Source
9+
10+See `src/lib.rs`.
11+
12+## Build
13+
14+```
15+wasm-pack build --target web
16+```
17+
18+## Serve
19+
20+Use any HTTP server, for example:
21+
22+```
23+python -m SimpleHTTPServer
24+```
examples/sierpinski-triangle/ferris.pngnew file

Binary file not shown.

examples/sierpinski-triangle/index.htmlnew file+69
@@ -0,0 +1,69 @@
1+<!DOCTYPE html>
2+<html>
3+
4+<head>
5+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
6+ <title>Sierpinski Triangle</title>
7+ <style>
8+ .container {
9+ position: absolute;
10+ transform-origin: 0 0;
11+ left: 50%;
12+ top: 50%;
13+ width: 10px;
14+ height: 10px;
15+ background: #eee;
16+ }
17+
18+ .dot {
19+ position: absolute;
20+ background: #61dafb;
21+ font: normal 15px sans-serif;
22+ text-align: center;
23+ cursor: pointer;
24+ }
25+
26+ .dot:hover {
27+ background: url("ferris.png") no-repeat center center;
28+ z-index: 999;
29+ }
30+
31+ code {
32+ position: relative;
33+ font-size: 150%;
34+ }
35+ </style>
36+</head>
37+
38+<body>
39+ <code>FPS: <output id="fps">...</output></code>
40+ <div id="scene"></div>
41+
42+ <script type="module">
43+ import init from "./pkg/dodrio_sierpinski.js";
44+ init("./pkg/dodrio_sierpinski_bg.wasm");
45+ </script>
46+
47+ <script>
48+ let lastTime = 0;
49+ let frameCount = 0;
50+ const fps = document.getElementById('fps');
51+
52+ function updateFPS(time) {
53+ frameCount++;
54+ if (lastTime + 1000.0 <= time) {
55+ fps.textContent = frameCount;
56+ lastTime = time;
57+ frameCount = 0;
58+ }
59+ }
60+
61+ requestAnimationFrame(function loop(now) {
62+ updateFPS(now);
63+ requestAnimationFrame(loop);
64+ })
65+
66+ </script>
67+</body>
68+
69+</html>
examples/sierpinski-triangle/src/lib.rsnew file+157
@@ -0,0 +1,157 @@
1+use dodrio::{bumpalo, Node, Render, RenderContext, Vdom};
2+use std::cell::RefCell;
3+use std::rc::Rc;
4+use wasm_bindgen::{prelude::*, JsCast};
5+use web_sys::Window;
6+
7+// Triangle container defining target size.
8+struct Container {
9+ size: f64,
10+}
11+
12+impl Container {
13+ // Construct a new container.
14+ pub fn new() -> Container {
15+ Container { size: 25.0 }
16+ }
17+
18+ // Generate the container style to fluctuate triangle width.
19+ fn container_transform<'a>(&self, cx: &mut RenderContext<'a>, elapsed: f64) -> &'a str {
20+ let t = elapsed % 10.0;
21+ let scale = 0.45 + (if t > 5.0 { 10.0 - t } else { t }) / 40.0;
22+ let transform = bumpalo::format!(
23+ in cx.bump,
24+ "transform: scaleX({}) scaleY(0.7) translateZ(0.1px)",
25+ scale
26+ );
27+ transform.into_bump_str()
28+ }
29+
30+ // Generate the dot's position on the grid.
31+ fn dot_style<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64) -> &'a str {
32+ let s = self.size * 1.3;
33+ let radius = s / 2.0;
34+ let styles = bumpalo::format!(
35+ in cx.bump,
36+ r#"
37+ width: {}px;
38+ height: {}px;
39+ left: {}px;
40+ top: {}px;
41+ border-radius: {}px;
42+ line-height: {}px;
43+ "#,
44+ s, s, x, y, radius, s
45+ );
46+ styles.into_bump_str()
47+ }
48+
49+ // Create a dot node.
50+ fn dot<'a>(&self, cx: &mut RenderContext<'a>, x: f64, y: f64, content: u32) -> Node<'a> {
51+ use dodrio::builder::{div, text};
52+
53+ div(&cx)
54+ .attr("class", "dot")
55+ .attr("style", self.dot_style(cx, x, y))
56+ .child(text(
57+ bumpalo::format!(in cx.bump, "{}", content).into_bump_str(),
58+ ))
59+ .finish()
60+ }
61+
62+ // Create a flattened vector of dot nodes (arranged in recursive triangles)
63+ fn triangle<'a>(
64+ &self,
65+ cx: &mut RenderContext<'a>,
66+ x: f64,
67+ y: f64,
68+ s: f64,
69+ content: u32,
70+ children: &mut bumpalo::collections::Vec<Node<'a>>,
71+ ) {
72+ if s <= self.size {
73+ children.push(self.dot(cx, x, y, content));
74+ return;
75+ }
76+
77+ let s = s / 2.0;
78+
79+ // Top of triangle
80+ self.triangle(cx, x, y - (s / 2.0), s, content, children);
81+ // Bottom-left of triangle
82+ self.triangle(cx, x - s, y + (s / 2.0), s, content, children);
83+ // Bottom-right of triangle
84+ self.triangle(cx, x + s, y + (s / 2.0), s, content, children);
85+ }
86+}
87+
88+impl<'a> Render<'a> for Container {
89+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
90+ use dodrio::builder::div;
91+
92+ let elapsed = web_sys::window()
93+ .unwrap_throw()
94+ .performance()
95+ .unwrap_throw()
96+ .now()
97+ / 1000.0;
98+
99+ let modulus = elapsed as u32 % 10;
100+
101+ let mut children = bumpalo::collections::Vec::new_in(cx.bump);
102+ self.triangle(cx, 0.0, 0.0, 1000.0, modulus, &mut children);
103+
104+ div(&cx)
105+ .attr("class", "container")
106+ .attr("style", self.container_transform(cx, elapsed))
107+ .children(children)
108+ .finish()
109+ }
110+}
111+
112+// Kick off a loop that keeps re-rendering on every animation frame.
113+fn animate(window: Window, vdom: Vdom) {
114+ let rc = <Rc<RefCell<Option<Closure<dyn FnMut()>>>>>::default();
115+ let rc2 = rc.clone();
116+ let window2 = window.clone();
117+ let weak = vdom.weak();
118+ let f = Closure::wrap(Box::new(move || {
119+ weak.schedule_render();
120+ window
121+ .request_animation_frame(
122+ rc.borrow()
123+ .as_ref()
124+ .unwrap_throw()
125+ .as_ref()
126+ .unchecked_ref::<js_sys::Function>(),
127+ )
128+ .unwrap_throw();
129+ }) as Box<dyn FnMut()>);
130+ window2
131+ .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
132+ .unwrap_throw();
133+ *rc2.borrow_mut() = Some(f);
134+
135+ // Run the virtual DOM and its listeners forever.
136+ vdom.forget();
137+}
138+
139+#[wasm_bindgen(start)]
140+pub fn run() {
141+ // Set up the panic hook
142+ console_error_panic_hook::set_once();
143+
144+ // Get the scene element to render within
145+ let window = web_sys::window().unwrap();
146+ let document = window.document().unwrap();
147+ let scene = document.get_element_by_id("scene").unwrap();
148+
149+ // Construct a new Container component.
150+ let container = Container::new();
151+
152+ // Mount our container component to the scene div.
153+ let vdom = Vdom::new(&scene, container);
154+
155+ // Kick off animation loop
156+ animate(window, vdom);
157+}
examples/todomvc/Cargo.tomlnew file+47
@@ -0,0 +1,47 @@
1+[package]
2+authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
3+edition = "2018"
4+name = "dodrio-todomvc"
5+version = "0.1.0"
6+
7+[dependencies]
8+cfg-if = "0.1.10"
9+dodrio = { path = "../..", features = ["serde"] }
10+js-sys = "0.3.32"
11+serde = { features = ["derive"], version = "1.0.114" }
12+serde_json = "1.0.57"
13+wasm-bindgen = "0.2.55"
14+wasm-bindgen-futures = "0.4.5"
15+
16+# Optional dependencies for logging.
17+console_error_panic_hook = { optional = true, version = "0.1.6" }
18+console_log = { optional = true, version = "0.2.0" }
19+log = { optional = true, version = "0.4.11" }
20+
21+[dependencies.web-sys]
22+version = "0.3.32"
23+features = [
24+ "Document",
25+ "Event",
26+ "EventTarget",
27+ "HtmlElement",
28+ "HtmlInputElement",
29+ "KeyboardEvent",
30+ "Location",
31+ "Storage",
32+ "Node",
33+ "Window",
34+]
35+
36+[dev-dependencies]
37+wasm-bindgen-test = "0.3.5"
38+
39+[features]
40+logging = [
41+ "console_log",
42+ "console_error_panic_hook",
43+ "log",
44+]
45+
46+[lib]
47+crate-type = ["cdylib"]
examples/todomvc/README.mdnew file+33
@@ -0,0 +1,33 @@
1+# TodoMVC
2+
3+`dodrio` implementation of the popular [TodoMVC](http://todomvc.com/) app. It
4+correctly and completely fulfills [the
5+specification](https://github.com/tastejs/todomvc/blob/master/app-spec.md) to
6+the best of my knowledge.
7+
8+## Source
9+
10+There are a number of modules in this `dodrio` implementation of TodoMVC. The
11+most important are:
12+
13+* `src/lib.rs`: The entry point to the application.
14+* `src/todos.rs`: Definition of `Todos` model and its rendering.
15+* `src/todo.rs`: Definition of `Todo` model and its rendering.
16+* `src/controller.rs`: The controller handles UI interactions and translates
17+ them into updates on the model. Finally, it triggers re-rendering after those
18+ updates.
19+* `src/router.rs`: A simple URL hash-based router.
20+
21+## Build
22+
23+```
24+wasm-pack build --target web
25+```
26+
27+## Serve
28+
29+Use any HTTP server, for example:
30+
31+```
32+python -m SimpleHTTPServer
33+```
examples/todomvc/index.htmlnew file+21
@@ -0,0 +1,21 @@
1+<!doctype html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta name="viewport" content="width=device-width, initial-scale=1">
6+ <title>dodrio • TodoMVC</title>
7+ <link rel="stylesheet" href="https://unpkg.com/todomvc-common@^1.0.0/base.css">
8+ <link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@^2.0.0/index.css">
9+ </head>
10+ <body>
11+ <section class="todoapp">
12+ </section>
13+ <footer class="info">
14+ <p>Double-click to edit a todo</p>
15+ </footer>
16+ <script type="module">
17+ import init from "./pkg/dodrio_todomvc.js";
18+ init("./pkg/dodrio_todomvc_bg.wasm");
19+ </script>
20+ </body>
21+</html>
examples/todomvc/src/controller.rsnew file+140
@@ -0,0 +1,140 @@
1+//! The controller handles UI events, translates them into updates on the model,
2+//! and schedules re-renders.
3+
4+use crate::todo::{Todo, TodoActions};
5+use crate::todos::{Todos, TodosActions};
6+use crate::visibility::Visibility;
7+use dodrio::{RootRender, VdomWeak};
8+use serde::{Deserialize, Serialize};
9+use std::ops::{Deref, DerefMut};
10+
11+/// The controller for the TodoMVC app.
12+///
13+/// This `Controller` struct is never actually instantiated. It is only used for
14+/// its `*Actions` trait implementations, none of which take a `self` parameter.
15+///
16+/// One could imagine alternative controller implementations with `*Actions`
17+/// trait implementations for (e.g.) testing that will assert various expected
18+/// action methods are called after rendering todo items and sending DOM events.
19+#[derive(Default, Deserialize, Serialize)]
20+pub struct Controller;
21+
22+impl TodosActions for Controller {
23+ fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak) {
24+ let mut todos = AutoCommitTodos::new(root, vdom);
25+ let all_complete = todos.todos().iter().all(|t| t.is_complete());
26+ for t in todos.todos_mut() {
27+ t.set_complete(!all_complete);
28+ }
29+ }
30+
31+ fn update_draft(root: &mut dyn RootRender, vdom: VdomWeak, draft: String) {
32+ let mut todos = AutoCommitTodos::new(root, vdom);
33+ todos.set_draft(draft);
34+ }
35+
36+ fn finish_draft(root: &mut dyn RootRender, vdom: VdomWeak) {
37+ let mut todos = AutoCommitTodos::new(root, vdom);
38+ let title = todos.take_draft();
39+ let title = title.trim();
40+ if !title.is_empty() {
41+ let id = todos.todos().len();
42+ let new = Todo::new(id, title);
43+ todos.add_todo(new);
44+ }
45+ }
46+
47+ fn change_visibility(root: &mut dyn RootRender, vdom: VdomWeak, vis: Visibility) {
48+ let mut todos = AutoCommitTodos::new(root, vdom);
49+ todos.set_visibility(vis);
50+ }
51+
52+ fn delete_completed(root: &mut dyn RootRender, vdom: VdomWeak) {
53+ let mut todos = AutoCommitTodos::new(root, vdom);
54+ todos.delete_completed();
55+ }
56+}
57+
58+impl TodoActions for Controller {
59+ fn toggle_completed(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
60+ let mut todos = AutoCommitTodos::new(root, vdom);
61+ let t = &mut todos.todos_mut()[id];
62+ let completed = t.is_complete();
63+ t.set_complete(!completed);
64+ }
65+
66+ fn delete(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
67+ let mut todos = AutoCommitTodos::new(root, vdom);
68+ todos.delete_todo(id);
69+ }
70+
71+ fn begin_editing(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
72+ let mut todos = AutoCommitTodos::new(root, vdom);
73+ let t = &mut todos.todos_mut()[id];
74+ let desc = t.title().to_string();
75+ t.set_edits(Some(desc));
76+ }
77+
78+ fn update_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize, edits: String) {
79+ let mut todos = AutoCommitTodos::new(root, vdom);
80+ let t = &mut todos.todos_mut()[id];
81+ t.set_edits(Some(edits));
82+ }
83+
84+ fn finish_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
85+ let mut todos = AutoCommitTodos::new(root, vdom);
86+ let t = &mut todos.todos_mut()[id];
87+ if let Some(edits) = t.take_edits() {
88+ let edits = edits.trim();
89+ if edits.is_empty() {
90+ todos.delete_todo(id);
91+ } else {
92+ t.set_title(edits);
93+ }
94+ }
95+ }
96+
97+ fn cancel_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
98+ let mut todos = AutoCommitTodos::new(root, vdom);
99+ let t = &mut todos.todos_mut()[id];
100+ let _ = t.take_edits();
101+ }
102+}
103+
104+/// An RAII type that dereferences to a `Todos` and once it is dropped, saves
105+/// the (presumably just modified) todos to local storage, and schedules a new
106+/// `dodrio` render.
107+pub struct AutoCommitTodos<'a> {
108+ todos: &'a mut Todos,
109+ vdom: VdomWeak,
110+}
111+
112+impl AutoCommitTodos<'_> {
113+ /// Construct a new `AutoCommitTodos` from the root rendering component and
114+ /// `vdom` handle.
115+ pub fn new(root: &mut dyn RootRender, vdom: VdomWeak) -> AutoCommitTodos {
116+ let todos = root.unwrap_mut::<Todos>();
117+ AutoCommitTodos { todos, vdom }
118+ }
119+}
120+
121+impl Drop for AutoCommitTodos<'_> {
122+ fn drop(&mut self) {
123+ self.todos.save_to_local_storage();
124+ self.vdom.schedule_render();
125+ }
126+}
127+
128+impl Deref for AutoCommitTodos<'_> {
129+ type Target = Todos;
130+
131+ fn deref(&self) -> &Todos {
132+ &self.todos
133+ }
134+}
135+
136+impl DerefMut for AutoCommitTodos<'_> {
137+ fn deref_mut(&mut self) -> &mut Todos {
138+ &mut self.todos
139+ }
140+}
examples/todomvc/src/keys.rsnew file+7
@@ -0,0 +1,7 @@
1+//! Constants for `KeyboardEvent::key_code`.`
2+
3+/// The key code for the enter key.
4+pub const ENTER: u32 = 13;
5+
6+/// The key code for the escape key.
7+pub const ESCAPE: u32 = 27;
examples/todomvc/src/lib.rsnew file+60
@@ -0,0 +1,60 @@
1+//! TodoMVC implemented in `dodrio`!
2+
3+#![deny(missing_docs)]
4+
5+pub mod controller;
6+pub mod keys;
7+pub mod router;
8+pub mod todo;
9+pub mod todos;
10+pub mod utils;
11+pub mod visibility;
12+
13+use crate::controller::Controller;
14+use crate::todos::Todos;
15+use dodrio::Vdom;
16+use wasm_bindgen::prelude::*;
17+
18+/// Run the TodoMVC app!
19+///
20+/// Since this is marked `#[wasm_bindgen(start)]` it is automatically invoked
21+/// once the wasm module instantiated on the Web page.
22+#[wasm_bindgen(start)]
23+pub fn run() -> Result<(), JsValue> {
24+ // Set up the logging for debugging if/when things go wrong.
25+ init_logging();
26+
27+ // Grab the todo app container.
28+ let document = utils::document();
29+ let container = document
30+ .query_selector(".todoapp")?
31+ .ok_or_else(|| js_sys::Error::new("could not find `.todoapp` container"))?;
32+
33+ // Create a new `Todos` render component.
34+ let todos = Todos::<Controller>::new();
35+
36+ // Create a virtual DOM and mount it and the `Todos` render component.
37+ let vdom = Vdom::new(&container, todos);
38+
39+ // Start the URL router.
40+ router::start(vdom.weak());
41+
42+ // Run the virtual DOM forever and don't unmount it.
43+ vdom.forget();
44+
45+ Ok(())
46+}
47+
48+cfg_if::cfg_if! {
49+ if #[cfg(feature = "logging")] {
50+ fn init_logging() {
51+ console_error_panic_hook::set_once();
52+ console_log::init_with_level(log::Level::Trace)
53+ .expect_throw("should initialize logging OK");
54+ }
55+ } else {
56+ fn init_logging() {
57+ // Do nothing.
58+ }
59+ }
60+}
examples/todomvc/src/router.rsnew file+68
@@ -0,0 +1,68 @@
1+//! A simple `#`-fragment router.
2+
3+use crate::todos::Todos;
4+use crate::utils;
5+use crate::visibility::Visibility;
6+use dodrio::VdomWeak;
7+use std::str::FromStr;
8+use wasm_bindgen::{prelude::*, JsCast};
9+
10+/// Start the router.
11+pub fn start(vdom: VdomWeak) {
12+ // Callback fired whenever the URL's hash fragment changes. Keeps the root
13+ // todos collection's visibility in sync with the `#` fragment.
14+ let on_hash_change = move || {
15+ let new_vis = utils::hash()
16+ .and_then(|hash| {
17+ if hash.starts_with("#/") {
18+ Visibility::from_str(&hash[2..]).ok()
19+ } else {
20+ None
21+ }
22+ })
23+ .unwrap_or_else(|| {
24+ // If we couldn't parse a visibility, make sure we canonicalize
25+ // it back to our default hash.
26+ let v = Visibility::default();
27+ utils::set_hash(&format!("#/{}", v));
28+ v
29+ });
30+
31+ wasm_bindgen_futures::spawn_local({
32+ let vdom = vdom.clone();
33+ async move {
34+ let _ = vdom
35+ .with_component({
36+ let vdom = vdom.clone();
37+ move |root| {
38+ let todos = root.unwrap_mut::<Todos>();
39+ // If the todos' visibility already matches the event's
40+ // visibility, then there is nothing to do (ha). If they
41+ // don't match, then we need to update the todos' visibility
42+ // and re-render.
43+ if todos.visibility() != new_vis {
44+ todos.set_visibility(new_vis);
45+ vdom.schedule_render();
46+ }
47+ }
48+ })
49+ .await;
50+ }
51+ });
52+ };
53+
54+ // Call it once to handle the initial `#` fragment.
55+ on_hash_change();
56+
57+ // Now listen for hash changes forever.
58+ //
59+ // Note that if we ever intended to unmount our todos app, we would want to
60+ // provide a method for removing this router's event listener and cleaning
61+ // up after ourselves.
62+ let on_hash_change = Closure::wrap(Box::new(on_hash_change) as Box<dyn FnMut()>);
63+ let window = utils::window();
64+ window
65+ .add_event_listener_with_callback("hashchange", on_hash_change.as_ref().unchecked_ref())
66+ .unwrap_throw();
67+ on_hash_change.forget();
68+}
examples/todomvc/src/todo.rsnew file+229
@@ -0,0 +1,229 @@
1+//! Type definition and `dodrio::Render` implementation for a single todo item.
2+
3+use crate::keys;
4+use dodrio::{Cached, Node, Render, RenderContext, RootRender, VdomWeak};
5+use serde::{Deserialize, Serialize};
6+use std::marker::PhantomData;
7+use wasm_bindgen::{prelude::*, JsCast};
8+
9+/// A single todo item.
10+#[derive(Default)]
11+pub struct Todo<C> {
12+ inner: Cached<TodoInner<C>>,
13+}
14+
15+#[derive(Serialize, Deserialize)]
16+struct TodoInner<C> {
17+ id: usize,
18+ title: String,
19+ completed: bool,
20+
21+ #[serde(skip)]
22+ edits: Option<String>,
23+
24+ #[serde(skip)]
25+ _controller: PhantomData<C>,
26+}
27+
28+/// Actions on a single todo item that can be triggered from the UI.
29+pub trait TodoActions {
30+ /// Toggle the completion state of the todo item with the given id.
31+ fn toggle_completed(root: &mut dyn RootRender, vdom: VdomWeak, id: usize);
32+
33+ /// Delete the todo item with the given id.
34+ fn delete(root: &mut dyn RootRender, vdom: VdomWeak, id: usize);
35+
36+ /// Begin editing the todo item with the given id.
37+ fn begin_editing(root: &mut dyn RootRender, vdom: VdomWeak, id: usize);
38+
39+ /// Update the edits for the todo with the given id.
40+ fn update_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize, edits: String);
41+
42+ /// Finish editing the todo with the given id.
43+ fn finish_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize);
44+
45+ /// Cancel editing the todo with the given id.
46+ fn cancel_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize);
47+}
48+
49+impl<C> Todo<C> {
50+ /// Construct a new `Todo` with the given identifier and title.
51+ pub fn new<S: Into<String>>(id: usize, title: S) -> Self {
52+ let title = title.into();
53+ let completed = false;
54+ let edits = None;
55+ Todo {
56+ inner: Cached::new(TodoInner {
57+ id,
58+ title,
59+ completed,
60+ edits,
61+ _controller: PhantomData,
62+ }),
63+ }
64+ }
65+
66+ /// Set this todo item's id.
67+ pub fn set_id(&mut self, id: usize) {
68+ if id != self.inner.id {
69+ Cached::invalidate(&self.inner);
70+ }
71+ self.inner.id = id;
72+ }
73+
74+ /// Is this `Todo` complete?
75+ pub fn is_complete(&self) -> bool {
76+ self.inner.completed
77+ }
78+
79+ /// Mark the `Todo` as complete or not.
80+ pub fn set_complete(&mut self, to: bool) {
81+ if to != self.inner.completed {
82+ Cached::invalidate(&self.inner);
83+ }
84+ self.inner.completed = to;
85+ }
86+
87+ /// Get this todo's title.
88+ pub fn title(&self) -> &str {
89+ &self.inner.title
90+ }
91+
92+ /// Set this todo item's title.
93+ pub fn set_title<S: Into<String>>(&mut self, title: S) {
94+ let title = title.into();
95+ if title != self.inner.title {
96+ Cached::invalidate(&self.inner);
97+ }
98+ self.inner.title = title;
99+ }
100+
101+ /// Set the edits for this todo.
102+ pub fn set_edits<S: Into<String>>(&mut self, edits: Option<S>) {
103+ let edits = edits.map(Into::into);
104+ if edits != self.inner.edits {
105+ Cached::invalidate(&self.inner);
106+ }
107+ self.inner.edits = edits;
108+ }
109+
110+ /// Take this todo's edits, leaving `None` in their place.
111+ pub fn take_edits(&mut self) -> Option<String> {
112+ if self.inner.edits.is_some() {
113+ Cached::invalidate(&self.inner);
114+ }
115+ self.inner.edits.take()
116+ }
117+}
118+
119+impl<'a, C: TodoActions> Render<'a> for Todo<C> {
120+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
121+ use dodrio::{
122+ builder::*,
123+ bumpalo::{self, collections::String},
124+ };
125+
126+ let id = self.inner.id;
127+ let title = self.inner.edits.as_ref().unwrap_or(&self.inner.title);
128+ let title = bumpalo::format!(in cx.bump, "{}", title).into_bump_str();
129+
130+ li(&cx)
131+ .attr("class", {
132+ let mut class = String::new_in(cx.bump);
133+ if self.inner.completed {
134+ class.push_str("completed ");
135+ }
136+ if self.inner.edits.is_some() {
137+ class.push_str("editing");
138+ }
139+ class.into_bump_str()
140+ })
141+ .children([
142+ div(&cx)
143+ .attr("class", "view")
144+ .children([
145+ input(&cx)
146+ .attr("class", "toggle")
147+ .attr("type", "checkbox")
148+ .bool_attr("checked", self.inner.completed)
149+ .on("click", move |root, vdom, _event| {
150+ C::toggle_completed(root, vdom, id);
151+ })
152+ .finish(),
153+ label(&cx)
154+ .on("dblclick", move |root, vdom, _event| {
155+ C::begin_editing(root, vdom, id);
156+ })
157+ .children([text(title)])
158+ .finish(),
159+ button(&cx)
160+ .attr("class", "destroy")
161+ .on("click", move |root, vdom, _event| {
162+ C::delete(root, vdom, id);
163+ })
164+ .finish(),
165+ ])
166+ .finish(),
167+ input(&cx)
168+ .attr("class", "edit")
169+ .attr("value", title)
170+ .attr("name", "title")
171+ .attr(
172+ "id",
173+ bumpalo::format!(in cx.bump, "todo-{}", id).into_bump_str(),
174+ )
175+ .on("input", move |root, vdom, event| {
176+ let input = event
177+ .target()
178+ .unwrap_throw()
179+ .unchecked_into::<web_sys::HtmlInputElement>();
180+ C::update_edits(root, vdom, id, input.value());
181+ })
182+ .on("blur", move |root, vdom, _event| {
183+ C::finish_edits(root, vdom, id);
184+ })
185+ .on("keydown", move |root, vdom, event| {
186+ let event = event.unchecked_into::<web_sys::KeyboardEvent>();
187+ match event.key_code() {
188+ keys::ENTER => C::finish_edits(root, vdom, id),
189+ keys::ESCAPE => C::cancel_edits(root, vdom, id),
190+ _ => {}
191+ }
192+ })
193+ .finish(),
194+ ])
195+ .finish()
196+ }
197+}
198+
199+impl<C> Default for TodoInner<C> {
200+ fn default() -> Self {
201+ TodoInner {
202+ id: Default::default(),
203+ title: Default::default(),
204+ completed: Default::default(),
205+ edits: Default::default(),
206+ _controller: PhantomData,
207+ }
208+ }
209+}
210+
211+impl<C> serde::Serialize for Todo<C> {
212+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
213+ where
214+ S: serde::Serializer,
215+ {
216+ self.inner.serialize(serializer)
217+ }
218+}
219+
220+impl<'de, C> Deserialize<'de> for Todo<C> {
221+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222+ where
223+ D: serde::Deserializer<'de>,
224+ {
225+ Ok(Todo {
226+ inner: <Cached<TodoInner<C>>>::deserialize(deserializer)?,
227+ })
228+ }
229+}
examples/todomvc/src/todos.rsnew file+300
@@ -0,0 +1,300 @@
1+//! Type definitions and `dodrio::Render` implementation for a collection of
2+//! todo items.
3+
4+use crate::controller::Controller;
5+use crate::todo::{Todo, TodoActions};
6+use crate::visibility::Visibility;
7+use crate::{keys, utils};
8+use dodrio::{bumpalo, Node, Render, RenderContext, RootRender, VdomWeak};
9+use serde::{Deserialize, Serialize};
10+use std::marker::PhantomData;
11+use std::mem;
12+use wasm_bindgen::{prelude::*, JsCast};
13+
14+/// A collection of todos.
15+#[derive(Default, Serialize, Deserialize)]
16+#[serde(rename = "todos-dodrio", bound = "")]
17+pub struct Todos<C = Controller> {
18+ todos: Vec<Todo<C>>,
19+
20+ #[serde(skip)]
21+ draft: String,
22+
23+ #[serde(skip)]
24+ visibility: Visibility,
25+
26+ #[serde(skip)]
27+ _controller: PhantomData<C>,
28+}
29+
30+/// Actions for `Todos` that can be triggered by UI interactions.
31+pub trait TodosActions: 'static + TodoActions {
32+ /// Toggle the completion state of all todo items.
33+ fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak);
34+
35+ /// Update the draft todo item's text.
36+ fn update_draft(root: &mut dyn RootRender, vdom: VdomWeak, draft: String);
37+
38+ /// Finish the current draft todo item and add it to the collection of
39+ /// todos.
40+ fn finish_draft(root: &mut dyn RootRender, vdom: VdomWeak);
41+
42+ /// Change the todo item visibility filtering to the given `Visibility`.
43+ fn change_visibility(root: &mut dyn RootRender, vdom: VdomWeak, vis: Visibility);
44+
45+ /// Delete all completed todo items.
46+ fn delete_completed(root: &mut dyn RootRender, vdom: VdomWeak);
47+}
48+
49+impl<C> Todos<C> {
50+ /// Construct a new todos set.
51+ ///
52+ /// If an existing set is available in local storage, then use that,
53+ /// otherwise create a new set.
54+ pub fn new() -> Self
55+ where
56+ C: Default,
57+ {
58+ Self::from_local_storage().unwrap_or_default()
59+ }
60+
61+ /// Deserialize a set of todos from local storage.
62+ pub fn from_local_storage() -> Option<Self> {
63+ utils::local_storage()
64+ .get("todomvc-dodrio")
65+ .ok()
66+ .and_then(|opt| opt)
67+ .and_then(|json| serde_json::from_str(&json).ok())
68+ }
69+
70+ /// Serialize this set of todos to local storage.
71+ pub fn save_to_local_storage(&self) {
72+ let serialized = serde_json::to_string(self).unwrap_throw();
73+ utils::local_storage()
74+ .set("todomvc-dodrio", &serialized)
75+ .unwrap_throw();
76+ }
77+
78+ /// Add a new todo item to this collection.
79+ pub fn add_todo(&mut self, todo: Todo<C>) {
80+ self.todos.push(todo);
81+ }
82+
83+ /// Delete the todo with the given id.
84+ pub fn delete_todo(&mut self, id: usize) {
85+ self.todos.remove(id);
86+ self.fix_ids();
87+ }
88+
89+ /// Delete all completed todo items.
90+ pub fn delete_completed(&mut self) {
91+ self.todos.retain(|t| !t.is_complete());
92+ self.fix_ids();
93+ }
94+
95+ // Fix all todo identifiers so that they match their index once again.
96+ fn fix_ids(&mut self) {
97+ for (id, todo) in self.todos.iter_mut().enumerate() {
98+ todo.set_id(id);
99+ }
100+ }
101+
102+ /// Get a shared slice of the underlying set of todo items.
103+ pub fn todos(&self) -> &[Todo<C>] {
104+ &self.todos
105+ }
106+
107+ /// Get an exclusive slice of the underlying set of todo items.
108+ pub fn todos_mut(&mut self) -> &mut [Todo<C>] {
109+ &mut self.todos
110+ }
111+
112+ /// Set the draft todo item text.
113+ pub fn set_draft<S: Into<String>>(&mut self, draft: S) {
114+ self.draft = draft.into();
115+ }
116+
117+ /// Take the current draft text and replace it with an empty string.
118+ pub fn take_draft(&mut self) -> String {
119+ mem::replace(&mut self.draft, String::new())
120+ }
121+
122+ /// Get the current visibility for these todos.
123+ pub fn visibility(&self) -> Visibility {
124+ self.visibility
125+ }
126+
127+ /// Set the visibility for these todoS.
128+ pub fn set_visibility(&mut self, vis: Visibility) {
129+ self.visibility = vis;
130+ }
131+}
132+
133+/// Rendering helpers.
134+impl<C: TodosActions> Todos<C> {
135+ fn header<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
136+ use dodrio::builder::*;
137+
138+ header(&cx)
139+ .attr("class", "header")
140+ .children([
141+ h1(&cx).children([text("todos")]).finish(),
142+ input(&cx)
143+ .on("input", |root, vdom, event| {
144+ let input = event
145+ .target()
146+ .unwrap_throw()
147+ .unchecked_into::<web_sys::HtmlInputElement>();
148+ C::update_draft(root, vdom, input.value());
149+ })
150+ .on("keydown", |root, vdom, event| {
151+ let event = event.unchecked_into::<web_sys::KeyboardEvent>();
152+ if event.key_code() == keys::ENTER {
153+ C::finish_draft(root, vdom);
154+ }
155+ })
156+ .attr("class", "new-todo")
157+ .attr("placeholder", "What needs to be done?")
158+ .attr("autofocus", "")
159+ .attr(
160+ "value",
161+ bumpalo::format!(in cx.bump, "{}", self.draft).into_bump_str(),
162+ )
163+ .finish(),
164+ ])
165+ .finish()
166+ }
167+
168+ fn todos_list<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
169+ use dodrio::{builder::*, bumpalo::collections::Vec};
170+
171+ let mut todos = Vec::with_capacity_in(self.todos.len(), cx.bump);
172+ todos.extend(
173+ self.todos
174+ .iter()
175+ .filter(|t| match self.visibility {
176+ Visibility::All => true,
177+ Visibility::Active => !t.is_complete(),
178+ Visibility::Completed => t.is_complete(),
179+ })
180+ .map(|t| t.render(cx)),
181+ );
182+
183+ section(&cx)
184+ .attr("class", "main")
185+ .attr(
186+ "visibility",
187+ if self.todos.is_empty() {
188+ "hidden"
189+ } else {
190+ "visible"
191+ },
192+ )
193+ .children([
194+ input(&cx)
195+ .attr("class", "toggle-all")
196+ .attr("id", "toggle-all")
197+ .attr("type", "checkbox")
198+ .attr("name", "toggle")
199+ .bool_attr("checked", self.todos.iter().all(|t| t.is_complete()))
200+ .on("click", |root, vdom, _event| {
201+ C::toggle_all(root, vdom);
202+ })
203+ .finish(),
204+ label(&cx)
205+ .attr("for", "toggle-all")
206+ .children([text("Mark all as complete")])
207+ .finish(),
208+ ul(&cx).attr("class", "todo-list").children(todos).finish(),
209+ ])
210+ .finish()
211+ }
212+
213+ fn footer<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
214+ use dodrio::builder::*;
215+
216+ let completed_count = self.todos.iter().filter(|t| t.is_complete()).count();
217+ let incomplete_count = self.todos.len() - completed_count;
218+ let items_left = if incomplete_count == 1 {
219+ " item left"
220+ } else {
221+ " items left"
222+ };
223+ let incomplete_count = bumpalo::format!(in cx.bump, "{}", incomplete_count);
224+
225+ let clear_completed_text = bumpalo::format!(
226+ in cx.bump,
227+ "Clear completed ({})",
228+ self.todos.iter().filter(|t| t.is_complete()).count()
229+ );
230+
231+ footer(&cx)
232+ .attr("class", "footer")
233+ .bool_attr("hidden", self.todos.is_empty())
234+ .children([
235+ span(&cx)
236+ .attr("class", "todo-count")
237+ .children([
238+ strong(&cx)
239+ .children([text(incomplete_count.into_bump_str())])
240+ .finish(),
241+ text(items_left),
242+ ])
243+ .finish(),
244+ ul(&cx)
245+ .attr("class", "filters")
246+ .children([
247+ self.visibility_swap(cx, "#/", Visibility::All),
248+ self.visibility_swap(cx, "#/active", Visibility::Active),
249+ self.visibility_swap(cx, "#/completed", Visibility::Completed),
250+ ])
251+ .finish(),
252+ button(&cx)
253+ .on("click", |root, vdom, _event| {
254+ C::delete_completed(root, vdom);
255+ })
256+ .attr("class", "clear-completed")
257+ .bool_attr("hidden", completed_count == 0)
258+ .children([text(clear_completed_text.into_bump_str())])
259+ .finish(),
260+ ])
261+ .finish()
262+ }
263+
264+ fn visibility_swap<'a>(
265+ &self,
266+ cx: &mut RenderContext<'a>,
267+ url: &'static str,
268+ target_vis: Visibility,
269+ ) -> Node<'a> {
270+ use dodrio::builder::*;
271+
272+ li(&cx)
273+ .on("click", move |root, vdom, _event| {
274+ C::change_visibility(root, vdom, target_vis);
275+ })
276+ .children([a(&cx)
277+ .attr("href", url)
278+ .attr(
279+ "class",
280+ if self.visibility == target_vis {
281+ "selected"
282+ } else {
283+ ""
284+ },
285+ )
286+ .children([text(target_vis.label())])
287+ .finish()])
288+ .finish()
289+ }
290+}
291+
292+impl<'a, C: TodosActions> Render<'a> for Todos<C> {
293+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
294+ use dodrio::builder::*;
295+
296+ div(&cx)
297+ .children([self.header(cx), self.todos_list(cx), self.footer(cx)])
298+ .finish()
299+ }
300+}
examples/todomvc/src/utils.rsnew file+32
@@ -0,0 +1,32 @@
1+//! Small utility functions.
2+
3+use wasm_bindgen::UnwrapThrowExt;
4+
5+/// Get the top-level window.
6+pub fn window() -> web_sys::Window {
7+ web_sys::window().unwrap_throw()
8+}
9+
10+/// Get the current location hash, if any.
11+pub fn hash() -> Option<String> {
12+ window()
13+ .location()
14+ .hash()
15+ .ok()
16+ .and_then(|h| if h.is_empty() { None } else { Some(h) })
17+}
18+
19+/// Set the current location hash.
20+pub fn set_hash(hash: &str) {
21+ window().location().set_hash(hash).unwrap_throw();
22+}
23+
24+/// Get the top-level document.
25+pub fn document() -> web_sys::Document {
26+ window().document().unwrap_throw()
27+}
28+
29+/// Get the top-level window's local storage.
30+pub fn local_storage() -> web_sys::Storage {
31+ window().local_storage().unwrap_throw().unwrap_throw()
32+}
examples/todomvc/src/visibility.rsnew file+51
@@ -0,0 +1,51 @@
1+//! Visibility filtering.
2+
3+use std::fmt;
4+use std::str::FromStr;
5+
6+/// The visibility filtering for todo items.
7+#[derive(Clone, Copy, Eq, PartialEq)]
8+pub enum Visibility {
9+ /// Show all todos.
10+ All,
11+ /// Show only active, incomplete todos.
12+ Active,
13+ /// Show only inactive, completed todos.
14+ Completed,
15+}
16+
17+impl Default for Visibility {
18+ fn default() -> Visibility {
19+ Visibility::All
20+ }
21+}
22+
23+impl FromStr for Visibility {
24+ type Err = ();
25+
26+ fn from_str(s: &str) -> Result<Self, Self::Err> {
27+ match s {
28+ "all" => Ok(Visibility::All),
29+ "active" => Ok(Visibility::Active),
30+ "completed" => Ok(Visibility::Completed),
31+ _ => Err(()),
32+ }
33+ }
34+}
35+
36+impl fmt::Display for Visibility {
37+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38+ write!(f, "{}", self.label().to_lowercase())
39+ }
40+}
41+
42+impl Visibility {
43+ /// Get a string label for this visibility.
44+ pub fn label(self) -> &'static str {
45+ match self {
46+ Visibility::All => "All",
47+ Visibility::Active => "Active",
48+ Visibility::Completed => "Completed",
49+ }
50+ }
51+}
js/change-list-interpreter.jsnew file+377
@@ -0,0 +1,377 @@
1+const decoder = new TextDecoder();
2+
3+function top(stack) {
4+ return stack[stack.length - 1];
5+}
6+
7+function string(mem, pointer, length) {
8+ const buf = mem.subarray(pointer, pointer + length);
9+ return decoder.decode(buf);
10+}
11+
12+const OP_TABLE = [
13+ // 0
14+ function setText(interpreter, mem8, mem32, i) {
15+ const pointer = mem32[i++];
16+ const length = mem32[i++];
17+ const str = string(mem8, pointer, length);
18+ top(interpreter.stack).textContent = str;
19+ return i;
20+ },
21+
22+ // 1
23+ function removeSelfAndNextSiblings(interpreter, mem8, mem32, i) {
24+ const node = interpreter.stack.pop();
25+ let sibling = node.nextSibling;
26+ while (sibling) {
27+ const temp = sibling.nextSibling;
28+ sibling.remove();
29+ sibling = temp;
30+ }
31+ node.remove();
32+ return i;
33+ },
34+
35+ // 2
36+ function replaceWith(interpreter, mem8, mem32, i) {
37+ const newNode = interpreter.stack.pop();
38+ const oldNode = interpreter.stack.pop();
39+ oldNode.replaceWith(newNode);
40+ interpreter.stack.push(newNode);
41+ return i;
42+ },
43+
44+ // 3
45+ function setAttribute(interpreter, mem8, mem32, i) {
46+ const nameId = mem32[i++];
47+ const valueId = mem32[i++];
48+ const name = interpreter.getCachedString(nameId);
49+ const value = interpreter.getCachedString(valueId);
50+ const node = top(interpreter.stack);
51+ node.setAttribute(name, value);
52+
53+ // Some attributes are "volatile" and don't work through `setAttribute`.
54+ if (name === "value") {
55+ node.value = value;
56+ }
57+ if (name === "checked") {
58+ node.checked = true;
59+ }
60+ if (name === "selected") {
61+ node.selected = true;
62+ }
63+
64+ return i;
65+ },
66+
67+ // 4
68+ function removeAttribute(interpreter, mem8, mem32, i) {
69+ const nameId = mem32[i++];
70+ const name = interpreter.getCachedString(nameId);
71+ const node = top(interpreter.stack);
72+ node.removeAttribute(name);
73+
74+ // Some attributes are "volatile" and don't work through `removeAttribute`.
75+ if (name === "value") {
76+ node.value = null;
77+ }
78+ if (name === "checked") {
79+ node.checked = false;
80+ }
81+ if (name === "selected") {
82+ node.selected = false;
83+ }
84+
85+ return i;
86+ },
87+
88+ // 5
89+ function pushReverseChild(interpreter, mem8, mem32, i) {
90+ const n = mem32[i++];
91+ const parent = top(interpreter.stack);
92+ const children = parent.childNodes;
93+ const child = children[children.length - n - 1];
94+ interpreter.stack.push(child);
95+ return i;
96+ },
97+
98+ // 6
99+ function popPushChild(interpreter, mem8, mem32, i) {
100+ const n = mem32[i++];
101+ interpreter.stack.pop();
102+ const parent = top(interpreter.stack);
103+ const children = parent.childNodes;
104+ const child = children[n];
105+ interpreter.stack.push(child);
106+ return i;
107+ },
108+
109+ // 7
110+ function pop(interpreter, mem8, mem32, i) {
111+ interpreter.stack.pop();
112+ return i;
113+ },
114+
115+ // 8
116+ function appendChild(interpreter, mem8, mem32, i) {
117+ const child = interpreter.stack.pop();
118+ top(interpreter.stack).appendChild(child);
119+ return i;
120+ },
121+
122+ // 9
123+ function createTextNode(interpreter, mem8, mem32, i) {
124+ const pointer = mem32[i++];
125+ const length = mem32[i++];
126+ const text = string(mem8, pointer, length);
127+ interpreter.stack.push(document.createTextNode(text));
128+ return i;
129+ },
130+
131+ // 10
132+ function createElement(interpreter, mem8, mem32, i) {
133+ const tagNameId = mem32[i++];
134+ const tagName = interpreter.getCachedString(tagNameId);
135+ interpreter.stack.push(document.createElement(tagName));
136+ return i;
137+ },
138+
139+ // 11
140+ function newEventListener(interpreter, mem8, mem32, i) {
141+ const eventId = mem32[i++];
142+ const eventType = interpreter.getCachedString(eventId);
143+ const a = mem32[i++];
144+ const b = mem32[i++];
145+ const el = top(interpreter.stack);
146+ el.addEventListener(eventType, interpreter.eventHandler);
147+ el[`dodrio-a-${eventType}`] = a;
148+ el[`dodrio-b-${eventType}`] = b;
149+ return i;
150+ },
151+
152+ // 12
153+ function updateEventListener(interpreter, mem8, mem32, i) {
154+ const eventId = mem32[i++];
155+ const eventType = interpreter.getCachedString(eventId);
156+ const el = top(interpreter.stack);
157+ el[`dodrio-a-${eventType}`] = mem32[i++];
158+ el[`dodrio-b-${eventType}`] = mem32[i++];
159+ return i;
160+ },
161+
162+ // 13
163+ function removeEventListener(interpreter, mem8, mem32, i) {
164+ const eventId = mem32[i++];
165+ const eventType = interpreter.getCachedString(eventId);
166+ const el = top(interpreter.stack);
167+ el.removeEventListener(eventType, interpreter.eventHandler);
168+ return i;
169+ },
170+
171+ // 14
172+ function addCachedString(interpreter, mem8, mem32, i) {
173+ const pointer = mem32[i++];
174+ const length = mem32[i++];
175+ const id = mem32[i++];
176+ const str = string(mem8, pointer, length);
177+ interpreter.addCachedString(str, id);
178+ return i;
179+ },
180+
181+ // 15
182+ function dropCachedString(interpreter, mem8, mem32, i) {
183+ const id = mem32[i++];
184+ interpreter.dropCachedString(id);
185+ return i;
186+ },
187+
188+ // 16
189+ function createElementNS(interpreter, mem8, mem32, i) {
190+ const tagNameId = mem32[i++];
191+ const tagName = interpreter.getCachedString(tagNameId);
192+ const nsId = mem32[i++];
193+ const ns = interpreter.getCachedString(nsId);
194+ interpreter.stack.push(document.createElementNS(ns, tagName));
195+ return i;
196+ },
197+
198+ // 17
199+ function saveChildrenToTemporaries(interpreter, mem8, mem32, i) {
200+ let temp = mem32[i++];
201+ const start = mem32[i++];
202+ const end = mem32[i++];
203+ const parent = top(interpreter.stack);
204+ const children = parent.childNodes;
205+ for (let i = start; i < end; i++) {
206+ interpreter.temporaries[temp++] = children[i];
207+ }
208+ return i;
209+ },
210+
211+ // 18
212+ function pushChild(interpreter, mem8, mem32, i) {
213+ const parent = top(interpreter.stack);
214+ const n = mem32[i++];
215+ const child = parent.childNodes[n];
216+ interpreter.stack.push(child);
217+ return i;
218+ },
219+
220+ // 19
221+ function pushTemporary(interpreter, mem8, mem32, i) {
222+ const temp = mem32[i++];
223+ interpreter.stack.push(interpreter.temporaries[temp]);
224+ return i;
225+ },
226+
227+ // 20
228+ function insertBefore(interpreter, mem8, mem32, i) {
229+ const before = interpreter.stack.pop();
230+ const after = interpreter.stack.pop();
231+ after.parentNode.insertBefore(before, after);
232+ interpreter.stack.push(before);
233+ return i;
234+ },
235+
236+ // 21
237+ function popPushReverseChild(interpreter, mem8, mem32, i) {
238+ const n = mem32[i++];
239+ interpreter.stack.pop();
240+ const parent = top(interpreter.stack);
241+ const children = parent.childNodes;
242+ const child = children[children.length - n - 1];
243+ interpreter.stack.push(child);
244+ return i;
245+ },
246+
247+ // 22
248+ function removeChild(interpreter, mem8, mem32, i) {
249+ const n = mem32[i++];
250+ const parent = top(interpreter.stack);
251+ const child = parent.childNodes[n];
252+ child.remove();
253+ return i;
254+ },
255+
256+ // 23
257+ function setClass(interpreter, mem8, mem32, i) {
258+ const classId = mem32[i++];
259+ const className = interpreter.getCachedString(classId);
260+ top(interpreter.stack).className = className;
261+ return i;
262+ },
263+
264+ // 24
265+ function saveTemplate(interpreter, mem8, mem32, i) {
266+ const id = mem32[i++];
267+ const template = top(interpreter.stack);
268+ interpreter.saveTemplate(id, template.cloneNode(true));
269+ return i;
270+ },
271+
272+ // 25
273+ function pushTemplate(interpreter, mem8, mem32, i) {
274+ const id = mem32[i++];
275+ const template = interpreter.getTemplate(id);
276+ interpreter.stack.push(template.cloneNode(true));
277+ return i;
278+ }
279+];
280+
281+export class ChangeListInterpreter {
282+ constructor(container) {
283+ this.trampoline = null;
284+ this.container = container;
285+ this.ranges = [];
286+ this.stack = [];
287+ this.strings = new Map();
288+ this.temporaries = [];
289+ this.templates = new Map();
290+ }
291+
292+ unmount() {
293+ this.trampoline.mounted = false;
294+
295+ // Null out all of our properties just to ensure that if we mistakenly ever
296+ // call a method on this instance again, it will throw.
297+ this.trampoline = null;
298+ this.container = null;
299+ this.ranges = null;
300+ this.stack = null;
301+ this.strings = null;
302+ this.temporaries = null;
303+ this.templates = null;
304+ }
305+
306+ addChangeListRange(start, len) {
307+ this.ranges.push(start);
308+ this.ranges.push(len);
309+ }
310+
311+ applyChanges(memory) {
312+ if (this.ranges.length == 0) {
313+ return;
314+ }
315+
316+ this.stack.push(this.container.firstChild);
317+ const mem8 = new Uint8Array(memory.buffer);
318+ const mem32 = new Uint32Array(memory.buffer);
319+
320+ for (let i = 0; i < this.ranges.length; i += 2) {
321+ const start = this.ranges[i];
322+ const len = this.ranges[i + 1];
323+ this.applyChangeRange(mem8, mem32, start, len);
324+ }
325+
326+ this.ranges.length = 0;
327+ this.stack.length = 0;
328+ this.temporaries.length = 0;
329+ }
330+
331+ applyChangeRange(mem8, mem32, start, len) {
332+ const end = (start + len) / 4;
333+ for (let i = start / 4; i < end; ) {
334+ const op = mem32[i++];
335+ i = OP_TABLE[op](this, mem8, mem32, i);
336+ }
337+ }
338+
339+ addCachedString(str, id) {
340+ this.strings.set(id, str);
341+ }
342+
343+ dropCachedString(id) {
344+ this.strings.delete(id);
345+ }
346+
347+ getCachedString(id) {
348+ return this.strings.get(id);
349+ }
350+
351+ saveTemplate(id, template) {
352+ this.templates.set(id, template);
353+ }
354+
355+ getTemplate(id) {
356+ return this.templates.get(id);
357+ }
358+
359+ initEventsTrampoline(trampoline) {
360+ this.trampoline = trampoline;
361+ trampoline.mounted = true;
362+ this.eventHandler = function(event) {
363+ if (!trampoline.mounted) {
364+ throw new Error("invocation of listener after VDOM has been unmounted");
365+ }
366+
367+ // `this` always refers to the element the handler was added to.
368+ // Since we're adding the handler to all elements our content wants
369+ // to listen for events on, this ensures that we always get the right
370+ // values for `a` and `b`.
371+ const type = event.type;
372+ const a = this[`dodrio-a-${type}`];
373+ const b = this[`dodrio-b-${type}`];
374+ trampoline(event, a, b);
375+ }
376+ }
377+}
js/change-list.jsdeleted−148
@@ -1,148 +0,0 @@
1−const decoder = new TextDecoder();
2−
3−function top(stack) {
4− return stack[stack.length - 1];
5−}
6−
7−function string(mem, pointer, length) {
8− const buf = mem.subarray(pointer, pointer + length);
9− return decoder.decode(buf);
10−}
11−
12−const OP_TABLE = [
13− // 0
14− function setText(stack, mem8, mem32, i) {
15− const pointer = mem32[i++];
16− const length = mem32[i++];
17− const str = string(mem8, pointer, length);
18− top(stack).textContent = str;
19− return i;
20− },
21−
22− // 1
23− function removeSelfAndNextSiblings(stack, mem8, mem32, i) {
24− const node = stack.pop();
25− let sibling = node.nextSibling;
26− while (sibling) {
27− const temp = sibling.nextSibling;
28− sibling.remove();
29− sibling = temp;
30− }
31− node.remove();
32− return i;
33− },
34−
35− // 2
36− function replaceWith(stack, mem8, mem32, i) {
37− const newNode = stack.pop();
38− const oldNode = stack.pop();
39− oldNode.replaceWith(newNode);
40− stack.push(newNode);
41− return i;
42− },
43−
44− // 3
45− function setAttribute(stack, mem8, mem32, i) {
46− const pointer1 = mem32[i++];
47− const length1 = mem32[i++];
48− const name = string(mem8, pointer1, length1);
49− const pointer2 = mem32[i++];
50− const length2 = mem32[i++];
51− const value = string(mem8, pointer2, length2);
52− top(stack).setAttribute(name, value);
53− return i;
54− },
55−
56− // 4
57− function removeAttribute(stack, mem8, mem32, i) {
58− const pointer = mem32[i++];
59− const length = mem32[i++];
60− const name = string(mem8, pointer, length);
61− top(stack).removeAttribute(name);
62− return i;
63− },
64−
65− // 5
66− function pushFirstChild(stack, mem8, mem32, i) {
67− stack.push(top(stack).firstChild);
68− return i;
69− },
70−
71− // 6
72− function popPushNextSibling(stack, mem8, mem32, i) {
73− const node = stack.pop();
74− stack.push(node.nextSibling);
75− return i;
76− },
77−
78− // 7
79− function pop(stack, mem8, mem32, i) {
80− stack.pop();
81− return i;
82− },
83−
84− // 8
85− function appendChild(stack, mem8, mem32, i) {
86− const child = stack.pop();
87− top(stack).appendChild(child);
88− return i;
89− },
90−
91− // 9
92− function createTextNode(stack, mem8, mem32, i) {
93− const pointer = mem32[i++];
94− const length = mem32[i++];
95− const text = string(mem8, pointer, length);
96− stack.push(document.createTextNode(text));
97− return i;
98− },
99−
100− // 10
101− function createElement(stack, mem8, mem32, i) {
102− const pointer = mem32[i++];
103− const length = mem32[i++];
104− const tagName = string(mem8, pointer, length);
105− stack.push(document.createElement(tagName));
106− return i;
107− }
108−];
109−
110−export class ChangeList {
111− constructor(container) {
112− this.container = container;
113− this.ranges = [];
114− this.stack = [];
115− }
116−
117− addChangeListRange(start, len) {
118− this.ranges.push(start);
119− this.ranges.push(len);
120− }
121−
122− applyChanges(memory) {
123− if (this.ranges.length == 0) {
124− return;
125− }
126−
127− this.stack.push(this.container.firstChild);
128− const mem8 = new Uint8Array(memory.buffer);
129− const mem32 = new Uint32Array(memory.buffer);
130−
131− for (let i = 0; i < this.ranges.length; i += 2) {
132− const start = this.ranges[i];
133− const len = this.ranges[i + 1];
134− this.applyChangeRange(mem8, mem32, start, len);
135− }
136−
137− this.ranges.length = 0;
138− this.stack.length = 0;
139− }
140−
141− applyChangeRange(mem8, mem32, start, len) {
142− const end = (start + len) / 4;
143− for (let i = start / 4; i < end; ) {
144− const op = mem32[i++];
145− i = OP_TABLE[op](this.stack, mem8, mem32, i);
146− }
147− }
148−}
js/package-lock.jsondeleted−5
@@ -1,5 +0,0 @@
1−{
2− "name": "dodrio",
3− "version": "0.1.0",
4− "lockfileVersion": 1
5−}
js/package.jsondeleted−11
@@ -1,11 +0,0 @@
1−{
2− "name": "dodrio",
3− "version": "0.1.0",
4− "description": "Dodrio",
5− "main": "dodrio.js",
6− "scripts": {
7− "test": "echo \"Error: no test specified\" && exit 1"
8− },
9− "author": "Nick Fitzgerald <fitzgen@gmail.com>",
10− "license": "MPL-2.0"
11−}
js/strace.jsnew file+80
@@ -0,0 +1,80 @@
1+export function initStrace() {
2+ instrumentAll(Element.prototype);
3+ instrumentAll(Document.prototype);
4+ instrumentAll(Node.prototype);
5+ instrumentAll(EventTarget.prototype);
6+}
7+
8+const counts = new Map;
9+counts.total = 0;
10+
11+let timer = null;
12+
13+function log(ctorName, kind, key) {
14+ counts.total += 1;
15+
16+ const entry = `${kind} ${ctorName}#${key}`
17+ const count = counts.get(entry);
18+ if (count === undefined) {
19+ counts.set(entry, 1);
20+ } else {
21+ counts.set(entry, count + 1);
22+ }
23+
24+ if (timer !== null) {
25+ clearTimeout(timer);
26+ }
27+ timer = setTimeout(dumpAndReset, 100);
28+}
29+
30+function dumpAndReset() {
31+ const data = [...counts]
32+ .sort((a, b) => b[1] - a[1])
33+ .map(a => ({ "DOM Method": a[0], "Count": a[1] }));
34+
35+ data.push({ "DOM Method": "<total>", "Count": counts.total });
36+
37+ console.table(data, ["DOM Method", "Count"]);
38+
39+ counts.clear();
40+ counts.total = 0;
41+}
42+
43+function instrument(proto, key, desc) {
44+ const ctorName = proto.constructor.name;
45+
46+ if (typeof desc.value == "function") {
47+ const f = desc.value;
48+ desc.value = function (...args) {
49+ log(ctorName, "call", key);
50+ return f.apply(this, args);
51+ };
52+ }
53+
54+ if (typeof desc.get == "function") {
55+ const get = desc.get;
56+ desc.get = function (...args) {
57+ log(ctorName, "get", key);
58+ return get.apply(this, args);
59+ };
60+ }
61+
62+ if (typeof desc.set == "function") {
63+ const set = desc.set;
64+ desc.set = function (...args) {
65+ log(ctorName, "set", key);
66+ return set.apply(this, args);
67+ };
68+ }
69+
70+ Object.defineProperty(proto, key, desc);
71+}
72+
73+function instrumentAll(proto) {
74+ for (const key of Object.keys(proto)) {
75+ const desc = Object.getOwnPropertyDescriptor(proto, key);
76+ if (desc.configurable) {
77+ instrument(proto, key, desc);
78+ }
79+ }
80+}
publish.shnew file+11
@@ -0,0 +1,11 @@
1+#!/usr/bin/env bash
2+
3+set -ex
4+
5+cd "$(dirname "$0")"
6+
7+cd crates/js-api
8+cargo publish --no-verify $PUBLISH_FLAGS
9+cd ../..
10+
11+cargo publish $PUBLISH_FLAGS
src/builder.rsnew file+1075
@@ -0,0 +1,1075 @@
1+//! Helpers for building virtual DOM nodes.
2+
3+use crate::{node::NodeKey, Attribute, Listener, Node, RootRender, VdomWeak};
4+use bumpalo::Bump;
5+
6+/// A virtual DOM element builder.
7+///
8+/// Typically constructed with element-specific constructors, eg the `div`
9+/// function for building `<div>` elements or the `button` function for building
10+/// `<button>` elements.
11+#[derive(Clone, Debug)]
12+pub struct ElementBuilder<'a, Listeners, Attributes, Children>
13+where
14+ Listeners: 'a + AsRef<[Listener<'a>]>,
15+ Attributes: 'a + AsRef<[Attribute<'a>]>,
16+ Children: 'a + AsRef<[Node<'a>]>,
17+{
18+ bump: &'a Bump,
19+ key: NodeKey,
20+ tag_name: &'a str,
21+ listeners: Listeners,
22+ attributes: Attributes,
23+ children: Children,
24+ namespace: Option<&'a str>,
25+}
26+
27+impl<'a>
28+ ElementBuilder<
29+ 'a,
30+ bumpalo::collections::Vec<'a, Listener<'a>>,
31+ bumpalo::collections::Vec<'a, Attribute<'a>>,
32+ bumpalo::collections::Vec<'a, Node<'a>>,
33+ >
34+{
35+ /// Create a new `ElementBuilder` for an element with the given tag name.
36+ ///
37+ /// In general, only use this constructor if the tag is dynamic (i.e. you
38+ /// might build a `<div>` or you might build a `<span>` and you don't know
39+ /// until runtime). Prefer using the tag-specific constructors instead:
40+ /// `div(bump)` or `span(bump)`, etc.
41+ ///
42+ /// # Example
43+ ///
44+ /// ```
45+ /// use dodrio::{builder::*, bumpalo::Bump};
46+ ///
47+ /// let b = Bump::new();
48+ ///
49+ /// let tag_name = if flip_coin() {
50+ /// "div"
51+ /// } else {
52+ /// "span"
53+ /// };
54+ ///
55+ /// let my_element_builder = ElementBuilder::new(&b, tag_name);
56+ /// # fn flip_coin() -> bool { true }
57+ /// ```
58+ pub fn new<B>(bump: B, tag_name: &'a str) -> Self
59+ where
60+ B: Into<&'a Bump>,
61+ {
62+ let bump = bump.into();
63+ ElementBuilder {
64+ bump,
65+ key: NodeKey::NONE,
66+ tag_name,
67+ listeners: bumpalo::collections::Vec::new_in(bump),
68+ attributes: bumpalo::collections::Vec::new_in(bump),
69+ children: bumpalo::collections::Vec::new_in(bump),
70+ namespace: None,
71+ }
72+ }
73+}
74+
75+impl<'a, Listeners, Attributes, Children> ElementBuilder<'a, Listeners, Attributes, Children>
76+where
77+ Listeners: 'a + AsRef<[Listener<'a>]>,
78+ Attributes: 'a + AsRef<[Attribute<'a>]>,
79+ Children: 'a + AsRef<[Node<'a>]>,
80+{
81+ /// Set the listeners for this element.
82+ ///
83+ /// You can use this method to customize the backing storage for listeners,
84+ /// for example to use a fixed-size array instead of the default
85+ /// dynamically-sized `bumpalo::collections::Vec`.
86+ ///
87+ /// Any listeners already added to the builder will be overridden.
88+ ///
89+ /// # Example
90+ ///
91+ /// ```no_run
92+ /// use dodrio::{builder::*, bumpalo::Bump};
93+ ///
94+ /// let b = Bump::new();
95+ ///
96+ /// // Create a `<div>` with a fixed-size array of two listeners.
97+ /// let my_div = div(&b)
98+ /// .listeners([
99+ /// on(&b, "click", |root, vdom, event| {
100+ /// // ...
101+ /// }),
102+ /// on(&b, "dblclick", |root, vdom, event| {
103+ /// // ...
104+ /// }),
105+ /// ])
106+ /// .finish();
107+ /// ```
108+ #[inline]
109+ pub fn listeners<L>(self, listeners: L) -> ElementBuilder<'a, L, Attributes, Children>
110+ where
111+ L: 'a + AsRef<[Listener<'a>]>,
112+ {
113+ ElementBuilder {
114+ bump: self.bump,
115+ key: self.key,
116+ tag_name: self.tag_name,
117+ listeners,
118+ attributes: self.attributes,
119+ children: self.children,
120+ namespace: self.namespace,
121+ }
122+ }
123+
124+ /// Set the attributes for this element.
125+ ///
126+ /// You can use this method to customize the backing storage for attributes,
127+ /// for example to use a fixed-size array instead of the default
128+ /// dynamically-sized `bumpalo::collections::Vec`.
129+ ///
130+ /// Any attributes already added to the builder will be overridden.
131+ ///
132+ /// # Example
133+ ///
134+ /// ```no_run
135+ /// use dodrio::{builder::*, bumpalo::Bump, Attribute};
136+ ///
137+ /// let b = Bump::new();
138+ ///
139+ /// // Create a `<div>` with a fixed-size array of two attributes.
140+ /// let my_div = div(&b)
141+ /// .attributes([
142+ /// attr("id", "my-div"),
143+ /// attr("class", "notification"),
144+ /// ])
145+ /// .finish();
146+ /// ```
147+ #[inline]
148+ pub fn attributes<A>(self, attributes: A) -> ElementBuilder<'a, Listeners, A, Children>
149+ where
150+ A: 'a + AsRef<[Attribute<'a>]>,
151+ {
152+ ElementBuilder {
153+ bump: self.bump,
154+ key: self.key,
155+ tag_name: self.tag_name,
156+ listeners: self.listeners,
157+ attributes,
158+ children: self.children,
159+ namespace: self.namespace,
160+ }
161+ }
162+
163+ /// Set the children for this element.
164+ ///
165+ /// You can use this method to customize the backing storage for children,
166+ /// for example to use a fixed-size array instead of the default
167+ /// dynamically-sized `bumpalo::collections::Vec`.
168+ ///
169+ /// Any children already added to the builder will be overridden.
170+ ///
171+ /// # Example
172+ ///
173+ /// ```no_run
174+ /// use dodrio::{builder::*, bumpalo::Bump};
175+ ///
176+ /// let b = Bump::new();
177+ ///
178+ /// // Create a `<div>` with a fixed-size array of two `<span>` children.
179+ /// let my_div = div(&b)
180+ /// .children([
181+ /// span(&b).finish(),
182+ /// span(&b).finish(),
183+ /// ])
184+ /// .finish();
185+ /// ```
186+ #[inline]
187+ pub fn children<C>(self, children: C) -> ElementBuilder<'a, Listeners, Attributes, C>
188+ where
189+ C: 'a + AsRef<[Node<'a>]>,
190+ {
191+ ElementBuilder {
192+ bump: self.bump,
193+ key: self.key,
194+ tag_name: self.tag_name,
195+ listeners: self.listeners,
196+ attributes: self.attributes,
197+ children,
198+ namespace: self.namespace,
199+ }
200+ }
201+
202+ /// Set the namespace for this element.
203+ ///
204+ /// # Example
205+ ///
206+ /// ```no_run
207+ /// use dodrio::{builder::*, bumpalo::Bump};
208+ ///
209+ /// let b = Bump::new();
210+ ///
211+ /// // Create a `<td>` tag with an xhtml namespace
212+ /// let my_td = td(&b)
213+ /// .namespace(Some("http://www.w3.org/1999/xhtml"))
214+ /// .finish();
215+ /// ```
216+ #[inline]
217+ pub fn namespace(self, namespace: Option<&'a str>) -> Self {
218+ ElementBuilder {
219+ bump: self.bump,
220+ key: self.key,
221+ tag_name: self.tag_name,
222+ listeners: self.listeners,
223+ attributes: self.attributes,
224+ children: self.children,
225+ namespace,
226+ }
227+ }
228+
229+ /// Set this element's key.
230+ ///
231+ /// When diffing sets of siblings, if an old sibling and new sibling share a
232+ /// key, then they will always reuse the same physical DOM node. This is
233+ /// important when using CSS animations, web components, third party JS, or
234+ /// anything else that makes the diffing implementation observable.
235+ ///
236+ /// Do not use keys if such a scenario does not apply. Keyed diffing is
237+ /// generally more expensive than not, since it is putting greater
238+ /// constraints on the diffing algorithm.
239+ ///
240+ /// # Invariants You Must Uphold
241+ ///
242+ /// The key may not be `u32::MAX`, which is a reserved key value.
243+ ///
244+ /// Keys must be unique among siblings.
245+ ///
246+ /// All sibling nodes must be keyed, or they must all not be keyed. You may
247+ /// not mix keyed and unkeyed siblings.
248+ ///
249+ /// # Example
250+ ///
251+ /// ```no_run
252+ /// use dodrio::{builder::*, bumpalo::Bump};
253+ ///
254+ /// let b = Bump::new();
255+ ///
256+ /// let my_li = li(&b)
257+ /// .key(1337)
258+ /// .finish();
259+ /// ```
260+ #[inline]
261+ pub fn key(mut self, key: u32) -> Self {
262+ use std::u32;
263+ debug_assert!(key != u32::MAX);
264+ self.key = NodeKey(key);
265+ self
266+ }
267+
268+ /// Create the virtual DOM node described by this builder.
269+ ///
270+ /// # Example
271+ ///
272+ /// ```no_run
273+ /// use dodrio::{builder::*, bumpalo::Bump, Node};
274+ ///
275+ /// let b = Bump::new();
276+ ///
277+ /// // Start with a builder...
278+ /// let builder: ElementBuilder<_, _, _> = div(&b);
279+ ///
280+ /// // ...and finish it to create a virtual DOM node!
281+ /// let my_div: Node = builder.finish();
282+ /// ```
283+ #[inline]
284+ pub fn finish(self) -> Node<'a> {
285+ let children: &'a Children = self.bump.alloc(self.children);
286+ let children: &'a [Node<'a>] = children.as_ref();
287+
288+ let listeners: &'a Listeners = self.bump.alloc(self.listeners);
289+ let listeners: &'a [Listener<'a>] = listeners.as_ref();
290+
291+ let attributes: &'a Attributes = self.bump.alloc(self.attributes);
292+ let attributes: &'a [Attribute<'a>] = attributes.as_ref();
293+
294+ Node::element(
295+ self.bump,
296+ self.key,
297+ self.tag_name,
298+ listeners,
299+ attributes,
300+ children,
301+ self.namespace,
302+ )
303+ }
304+}
305+
306+impl<'a, Attributes, Children>
307+ ElementBuilder<'a, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
308+where
309+ Attributes: 'a + AsRef<[Attribute<'a>]>,
310+ Children: 'a + AsRef<[Node<'a>]>,
311+{
312+ /// Add a new event listener to this element.
313+ ///
314+ /// The `event` string specifies which event will be listened for. The
315+ /// `callback` function is the function that will be invoked if the
316+ /// specified event occurs.
317+ ///
318+ /// # Example
319+ ///
320+ /// ```no_run
321+ /// use dodrio::{builder::*, bumpalo::Bump};
322+ ///
323+ /// let b = Bump::new();
324+ ///
325+ /// // A button that does something when clicked!
326+ /// let my_button = button(&b)
327+ /// .on("click", |root, vdom, event| {
328+ /// // ...
329+ /// })
330+ /// .finish();
331+ /// ```
332+ #[inline]
333+ pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
334+ where
335+ F: 'static + Fn(&mut dyn RootRender, VdomWeak, web_sys::Event),
336+ {
337+ self.listeners.push(Listener {
338+ event,
339+ callback: self.bump.alloc(callback),
340+ });
341+ self
342+ }
343+}
344+
345+impl<'a, Listeners, Children>
346+ ElementBuilder<'a, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
347+where
348+ Listeners: 'a + AsRef<[Listener<'a>]>,
349+ Children: 'a + AsRef<[Node<'a>]>,
350+{
351+ /// Add a new attribute to this element.
352+ ///
353+ /// # Example
354+ ///
355+ /// ```no_run
356+ /// use dodrio::{builder::*, bumpalo::Bump};
357+ ///
358+ /// let b = Bump::new();
359+ ///
360+ /// // Create the `<div id="my-div"/>` element.
361+ /// let my_div = div(&b).attr("id", "my-div").finish();
362+ /// ```
363+ #[inline]
364+ pub fn attr(mut self, name: &'a str, value: &'a str) -> Self {
365+ self.attributes.push(Attribute { name, value });
366+ self
367+ }
368+
369+ /// Conditionally add a "boolean-style" attribute to this element.
370+ ///
371+ /// If the `should_add` parameter is true, then adds an attribute with the
372+ /// given `name` and an empty string value. If the `should_add` parameter is
373+ /// false, then the attribute is not added.
374+ ///
375+ /// This method is useful for attributes whose semantics are defined by
376+ /// whether or not the attribute is present or not, and whose value is
377+ /// ignored. Example attributes like this include:
378+ ///
379+ /// * `checked`
380+ /// * `hidden`
381+ /// * `selected`
382+ ///
383+ /// # Example
384+ ///
385+ /// ```no_run
386+ /// use dodrio::{builder::*, bumpalo::Bump};
387+ /// use js_sys::Math;
388+ ///
389+ /// let b = Bump::new();
390+ ///
391+ /// // Create the `<div>` that is randomly hidden 50% of the time.
392+ /// let my_div = div(&b)
393+ /// .bool_attr("hidden", Math::random() >= 0.5)
394+ /// .finish();
395+ /// ```
396+ pub fn bool_attr(mut self, name: &'a str, should_add: bool) -> Self {
397+ if should_add {
398+ self.attributes.push(Attribute { name, value: "" });
399+ }
400+ self
401+ }
402+}
403+
404+impl<'a, Listeners, Attributes>
405+ ElementBuilder<'a, Listeners, Attributes, bumpalo::collections::Vec<'a, Node<'a>>>
406+where
407+ Listeners: 'a + AsRef<[Listener<'a>]>,
408+ Attributes: 'a + AsRef<[Attribute<'a>]>,
409+{
410+ /// Add a new child to this element.
411+ ///
412+ /// # Example
413+ ///
414+ /// ```no_run
415+ /// use dodrio::{builder::*, bumpalo::Bump};
416+ /// use js_sys::Math;
417+ ///
418+ /// let b = Bump::new();
419+ ///
420+ /// // Create `<p><span></span></p>`.
421+ /// let my_div = p(&b)
422+ /// .child(span(&b).finish())
423+ /// .finish();
424+ /// ```
425+ #[inline]
426+ pub fn child(mut self, child: Node<'a>) -> Self {
427+ self.children.push(child);
428+ self
429+ }
430+}
431+
432+macro_rules! builder_constructors {
433+ ( $(
434+ $(#[$attr:meta])*
435+ $name:ident;
436+ )* ) => {
437+ $(
438+ $(#[$attr])*
439+ #[inline]
440+ pub fn $name<'a, B>(
441+ bump: B,
442+ ) -> ElementBuilder<
443+ 'a,
444+ bumpalo::collections::Vec<'a, Listener<'a>>,
445+ bumpalo::collections::Vec<'a, Attribute<'a>>,
446+ bumpalo::collections::Vec<'a, Node<'a>>,
447+ >
448+ where
449+ B: Into<&'a Bump>
450+ {
451+ ElementBuilder::new(bump, stringify!($name))
452+ }
453+ )*
454+ };
455+ ( $(
456+ $(#[$attr:meta])*
457+ $name:ident <> $namespace:tt;
458+ )* ) => {
459+ $(
460+ $(#[$attr])*
461+ #[inline]
462+ pub fn $name<'a>(
463+ bump: &'a Bump,
464+ ) -> ElementBuilder<
465+ 'a,
466+ bumpalo::collections::Vec<'a, Listener<'a>>,
467+ bumpalo::collections::Vec<'a, Attribute<'a>>,
468+ bumpalo::collections::Vec<'a, Node<'a>>,
469+ > {
470+ let builder = ElementBuilder::new(bump, stringify!($name));
471+ builder.namespace(Some($namespace))
472+ }
473+ )*
474+ }
475+}
476+
477+// Organized in the same order as
478+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
479+//
480+// Does not include obsolete elements.
481+builder_constructors! {
482+ // Document metadata
483+
484+ /// Build a
485+ /// [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
486+ /// element.
487+ base;
488+ /// Build a
489+ /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
490+ /// element.
491+ head;
492+ /// Build a
493+ /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
494+ /// element.
495+ link;
496+ /// Build a
497+ /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
498+ /// element.
499+ meta;
500+ /// Build a
501+ /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
502+ /// element.
503+ style;
504+ /// Build a
505+ /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)
506+ /// element.
507+ title;
508+
509+ // Sectioning root
510+
511+ /// Build a
512+ /// [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
513+ /// element.
514+ body;
515+
516+ // Content sectioning
517+
518+ /// Build a
519+ /// [`<address>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address)
520+ /// element.
521+ address;
522+ /// Build a
523+ /// [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)
524+ /// element.
525+ article;
526+ /// Build a
527+ /// [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside)
528+ /// element.
529+ aside;
530+ /// Build a
531+ /// [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer)
532+ /// element.
533+ footer;
534+ /// Build a
535+ /// [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header)
536+ /// element.
537+ header;
538+ /// Build a
539+ /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
540+ /// element.
541+ h1;
542+ /// Build a
543+ /// [`<h2>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2)
544+ /// element.
545+ h2;
546+ /// Build a
547+ /// [`<h3>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3)
548+ /// element.
549+ h3;
550+ /// Build a
551+ /// [`<h4>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4)
552+ /// element.
553+ h4;
554+ /// Build a
555+ /// [`<h5>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5)
556+ /// element.
557+ h5;
558+ /// Build a
559+ /// [`<h6>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6)
560+ /// element.
561+ h6;
562+ /// Build a
563+ /// [`<hgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup)
564+ /// element.
565+ hgroup;
566+ /// Build a
567+ /// [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main)
568+ /// element.
569+ main;
570+ /// Build a
571+ /// [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
572+ /// element.
573+ nav;
574+ /// Build a
575+ /// [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)
576+ /// element.
577+ section;
578+
579+ // Text content
580+
581+ /// Build a
582+ /// [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote)
583+ /// element.
584+ blockquote;
585+ /// Build a
586+ /// [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd)
587+ /// element.
588+ dd;
589+ /// Build a
590+ /// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)
591+ /// element.
592+ div;
593+ /// Build a
594+ /// [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)
595+ /// element.
596+ dl;
597+ /// Build a
598+ /// [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt)
599+ /// element.
600+ dt;
601+ /// Build a
602+ /// [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption)
603+ /// element.
604+ figcaption;
605+ /// Build a
606+ /// [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
607+ /// element.
608+ figure;
609+ /// Build a
610+ /// [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr)
611+ /// element.
612+ hr;
613+ /// Build a
614+ /// [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)
615+ /// element.
616+ li;
617+ /// Build a
618+ /// [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol)
619+ /// element.
620+ ol;
621+ /// Build a
622+ /// [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)
623+ /// element.
624+ p;
625+ /// Build a
626+ /// [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre)
627+ /// element.
628+ pre;
629+ /// Build a
630+ /// [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)
631+ /// element.
632+ ul;
633+
634+ // Inline text semantics
635+
636+ /// Build a
637+ /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
638+ /// element.
639+ a;
640+ /// Build a
641+ /// [`<abbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr)
642+ /// element.
643+ abbr;
644+ /// Build a
645+ /// [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b)
646+ /// element.
647+ b;
648+ /// Build a
649+ /// [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi)
650+ /// element.
651+ bdi;
652+ /// Build a
653+ /// [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo)
654+ /// element.
655+ bdo;
656+ /// Build a
657+ /// [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br)
658+ /// element.
659+ br;
660+ /// Build a
661+ /// [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite)
662+ /// element.
663+ cite;
664+ /// Build a
665+ /// [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code)
666+ /// element.
667+ code;
668+ /// Build a
669+ /// [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data)
670+ /// element.
671+ data;
672+ /// Build a
673+ /// [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn)
674+ /// element.
675+ dfn;
676+ /// Build a
677+ /// [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em)
678+ /// element.
679+ em;
680+ /// Build a
681+ /// [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i)
682+ /// element.
683+ i;
684+ /// Build a
685+ /// [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd)
686+ /// element.
687+ kbd;
688+ /// Build a
689+ /// [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark)
690+ /// element.
691+ mark;
692+ /// Build a
693+ /// [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q)
694+ /// element.
695+ q;
696+ /// Build a
697+ /// [`<rb>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rb)
698+ /// element.
699+ rb;
700+ /// Build a
701+ /// [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp)
702+ /// element.
703+ rp;
704+ /// Build a
705+ /// [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt)
706+ /// element.
707+ rt;
708+ /// Build a
709+ /// [`<rtc>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rtc)
710+ /// element.
711+ rtc;
712+ /// Build a
713+ /// [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby)
714+ /// element.
715+ ruby;
716+ /// Build a
717+ /// [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s)
718+ /// element.
719+ s;
720+ /// Build a
721+ /// [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp)
722+ /// element.
723+ samp;
724+ /// Build a
725+ /// [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small)
726+ /// element.
727+ small;
728+ /// Build a
729+ /// [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span)
730+ /// element.
731+ span;
732+ /// Build a
733+ /// [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)
734+ /// element.
735+ strong;
736+ /// Build a
737+ /// [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub)
738+ /// element.
739+ sub;
740+ /// Build a
741+ /// [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup)
742+ /// element.
743+ sup;
744+ /// Build a
745+ /// [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)
746+ /// element.
747+ time;
748+ /// Build a
749+ /// [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u)
750+ /// element.
751+ u;
752+ /// Build a
753+ /// [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var)
754+ /// element.
755+ var;
756+ /// Build a
757+ /// [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr)
758+ /// element.
759+ wbr;
760+
761+ // Image and multimedia
762+
763+ /// Build a
764+ /// [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)
765+ /// element.
766+ area;
767+ /// Build a
768+ /// [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)
769+ /// element.
770+ audio;
771+ /// Build a
772+ /// [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
773+ /// element.
774+ img;
775+ /// Build a
776+ /// [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map)
777+ /// element.
778+ map;
779+ /// Build a
780+ /// [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track)
781+ /// element.
782+ track;
783+ /// Build a
784+ /// [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)
785+ /// element.
786+ video;
787+
788+ // Embedded content
789+
790+ /// Build a
791+ /// [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed)
792+ /// element.
793+ embed;
794+ /// Build a
795+ /// [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
796+ /// element.
797+ iframe;
798+ /// Build a
799+ /// [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)
800+ /// element.
801+ object;
802+ /// Build a
803+ /// [`<param>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param)
804+ /// element.
805+ param;
806+ /// Build a
807+ /// [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
808+ /// element.
809+ picture;
810+ /// Build a
811+ /// [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source)
812+ /// element.
813+ source;
814+
815+ // Scripting
816+
817+ /// Build a
818+ /// [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
819+ /// element.
820+ canvas;
821+ /// Build a
822+ /// [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript)
823+ /// element.
824+ noscript;
825+ /// Build a
826+ /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
827+ /// element.
828+ script;
829+
830+ // Demarcating edits
831+
832+ /// Build a
833+ /// [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del)
834+ /// element.
835+ del;
836+ /// Build a
837+ /// [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins)
838+ /// element.
839+ ins;
840+
841+ // Table content
842+
843+ /// Build a
844+ /// [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption)
845+ /// element.
846+ caption;
847+ /// Build a
848+ /// [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col)
849+ /// element.
850+ col;
851+ /// Build a
852+ /// [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup)
853+ /// element.
854+ colgroup;
855+ /// Build a
856+ /// [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)
857+ /// element.
858+ table;
859+ /// Build a
860+ /// [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody)
861+ /// element.
862+ tbody;
863+ /// Build a
864+ /// [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)
865+ /// element.
866+ td;
867+ /// Build a
868+ /// [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot)
869+ /// element.
870+ tfoot;
871+ /// Build a
872+ /// [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th)
873+ /// element.
874+ th;
875+ /// Build a
876+ /// [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead)
877+ /// element.
878+ thead;
879+ /// Build a
880+ /// [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr)
881+ /// element.
882+ tr;
883+
884+ // Forms
885+
886+ /// Build a
887+ /// [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)
888+ /// element.
889+ button;
890+ /// Build a
891+ /// [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist)
892+ /// element.
893+ datalist;
894+ /// Build a
895+ /// [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)
896+ /// element.
897+ fieldset;
898+ /// Build a
899+ /// [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
900+ /// element.
901+ form;
902+ /// Build a
903+ /// [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
904+ /// element.
905+ input;
906+ /// Build a
907+ /// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
908+ /// element.
909+ label;
910+ /// Build a
911+ /// [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)
912+ /// element.
913+ legend;
914+ /// Build a
915+ /// [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter)
916+ /// element.
917+ meter;
918+ /// Build a
919+ /// [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup)
920+ /// element.
921+ optgroup;
922+ /// Build a
923+ /// [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
924+ /// element.
925+ option;
926+ /// Build a
927+ /// [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)
928+ /// element.
929+ output;
930+ /// Build a
931+ /// [`<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress)
932+ /// element.
933+ progress;
934+ /// Build a
935+ /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
936+ /// element.
937+ select;
938+ /// Build a
939+ /// [`<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)
940+ /// element.
941+ textarea;
942+
943+ // Interactive elements
944+
945+ /// Build a
946+ /// [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
947+ /// element.
948+ details;
949+ /// Build a
950+ /// [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)
951+ /// element.
952+ dialog;
953+ /// Build a
954+ /// [`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu)
955+ /// element.
956+ menu;
957+ /// Build a
958+ /// [`<menuitem>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menuitem)
959+ /// element.
960+ menuitem;
961+ /// Build a
962+ /// [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)
963+ /// element.
964+ summary;
965+
966+ // Web components
967+
968+ /// Build a
969+ /// [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)
970+ /// element.
971+ slot;
972+ /// Build a
973+ /// [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)
974+ /// element.
975+ template;
976+}
977+
978+builder_constructors! {
979+ // SVG components
980+
981+ /// Build a
982+ /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
983+ /// element.
984+ svg <> "http://www.w3.org/2000/svg" ;
985+ /// Build a
986+ /// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
987+ /// element.
988+ path <> "http://www.w3.org/2000/svg";
989+ /// Build a
990+ /// [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)
991+ /// element.
992+ circle <> "http://www.w3.org/2000/svg";
993+ /// Build a
994+ /// [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse)
995+ /// element.
996+ ellipse <> "http://www.w3.org/2000/svg";
997+ /// Build a
998+ /// [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
999+ /// element.
1000+ line <> "http://www.w3.org/2000/svg";
1001+ /// Build a
1002+ /// [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)
1003+ /// element.
1004+ polygon <> "http://www.w3.org/2000/svg";
1005+ /// Build a
1006+ /// [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)
1007+ /// element.
1008+ polyline <> "http://www.w3.org/2000/svg";
1009+ /// Build a
1010+ /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
1011+ /// element.
1012+ rect <> "http://www.w3.org/2000/svg";
1013+ /// Build a
1014+ /// [`<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image)
1015+ /// element.
1016+ image <> "http://www.w3.org/2000/svg";
1017+}
1018+
1019+/// Construct a text node.
1020+///
1021+/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
1022+///
1023+/// # Example
1024+///
1025+/// ```no_run
1026+/// use dodrio::builder::*;
1027+///
1028+/// let my_text = text("hello, dodrio!");
1029+/// ```
1030+#[inline]
1031+pub fn text<'a>(contents: &'a str) -> Node<'a> {
1032+ Node::text(contents)
1033+}
1034+
1035+/// Construct an attribute for an element.
1036+///
1037+/// # Example
1038+///
1039+/// This example creates the `id="my-id"` for some element like `<div
1040+/// id="my-id"/>`.
1041+///
1042+/// ```no_run
1043+/// use dodrio::builder::*;
1044+///
1045+/// let my_id_attr = attr("id", "my-id");
1046+/// ```
1047+pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
1048+ Attribute { name, value }
1049+}
1050+
1051+/// Create an event listener.
1052+///
1053+/// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
1054+/// is the function that will be invoked when the event occurs.
1055+///
1056+/// # Example
1057+///
1058+/// ```no_run
1059+/// use dodrio::{builder::*, bumpalo::Bump};
1060+///
1061+/// let b = Bump::new();
1062+///
1063+/// let listener = on(&b, "click", |root, vdom, event| {
1064+/// // do something when a click happens...
1065+/// });
1066+/// ```
1067+pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
1068+where
1069+ F: Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static,
1070+{
1071+ Listener {
1072+ event,
1073+ callback: bump.alloc(callback),
1074+ }
1075+}
src/cached.rsnew file+229
@@ -0,0 +1,229 @@
1+use crate::{
2+ cached_set::CachedSet,
3+ node::{CachedNode, NodeKey},
4+ Node, Render, RenderContext,
5+};
6+use std::any::TypeId;
7+use std::cell::Cell;
8+use std::ops::{Deref, DerefMut};
9+
10+/// A renderable that supports caching for when rendering is expensive but can
11+/// generate the same DOM tree.
12+///
13+/// # Example
14+///
15+/// The [TodoMVC
16+/// example](https://github.com/fitzgen/dodrio/blob/master/examples/todomvc/src/todo.rs)
17+/// caches individual todo items.
18+#[derive(Clone, Debug, Default)]
19+pub struct Cached<R>
20+where
21+ R: Default,
22+{
23+ inner: R,
24+ cached: Cell<Option<CachedNode>>,
25+}
26+
27+pub_unstable_internal! {
28+ #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
29+ pub(crate) struct TemplateId(TypeId);
30+}
31+
32+impl<R> Cached<R>
33+where
34+ R: Default,
35+{
36+ /// Construct a new `Cached<R>` of an inner `R`.
37+ ///
38+ /// # Example
39+ ///
40+ /// ```
41+ /// use dodrio::{Cached, Node, Render, RenderContext};
42+ ///
43+ /// #[derive(Default)]
44+ /// pub struct Counter {
45+ /// count: u32,
46+ /// }
47+ ///
48+ /// impl<'a> Render<'a> for Counter {
49+ /// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
50+ /// // ...
51+ /// # unimplemented!()
52+ /// }
53+ /// }
54+ ///
55+ /// // Create a render-able counter.
56+ /// let counter = Counter { count: 0 };
57+ ///
58+ /// // And cache its rendering!
59+ /// let cached_counter = Cached::new(counter);
60+ /// ```
61+ #[inline]
62+ pub fn new(inner: R) -> Cached<R> {
63+ let cached = Cell::new(None);
64+ Cached { inner, cached }
65+ }
66+
67+ /// Invalidate the cached rendering.
68+ ///
69+ /// This method should be called whenever the inner `R` must be re-rendered,
70+ /// and the cached `Node` from the last time `R::render` was invoked can no
71+ /// longer be re-used.
72+ ///
73+ /// # Example
74+ ///
75+ /// The `Cached<Hello>` component must have its cache invalidated whenever
76+ /// the `who` string is changed, or else the cached rendering will keep
77+ /// displaying greetings to old `who`s.
78+ ///
79+ /// ```
80+ /// use dodrio::{bumpalo, Cached, Node, Render, RenderContext};
81+ ///
82+ /// /// A component that renders to "<p>Hello, {who}!</p>"
83+ /// #[derive(Default)]
84+ /// pub struct Hello {
85+ /// who: String
86+ /// }
87+ ///
88+ /// impl<'a> Render<'a> for Hello {
89+ /// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
90+ /// use dodrio::builder::*;
91+ /// let greeting = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
92+ /// p(&cx)
93+ /// .children([text(greeting.into_bump_str())])
94+ /// .finish()
95+ /// }
96+ /// }
97+ ///
98+ /// /// Whenever a `Cached<Hello>`'s `who` is updated, we need to invalidate the
99+ /// /// cache so that we don't keep displaying greetings to old `who`s.
100+ /// pub fn set_who(hello: &mut Cached<Hello>, who: String) {
101+ /// hello.who = who;
102+ /// Cached::invalidate(hello);
103+ /// }
104+ /// ```
105+ #[inline]
106+ pub fn invalidate(cached: &Self) {
107+ cached.cached.set(None);
108+ }
109+
110+ /// Convert a `Cached<R>` back into a plain `R`.
111+ #[inline]
112+ pub fn into_inner(cached: Self) -> R {
113+ cached.inner
114+ }
115+}
116+
117+impl<R> Cached<R>
118+where
119+ R: 'static + Default,
120+{
121+ pub(crate) fn template_id() -> TemplateId {
122+ TemplateId(TypeId::of::<R>())
123+ }
124+}
125+
126+impl<R> Deref for Cached<R>
127+where
128+ R: Default,
129+{
130+ type Target = R;
131+
132+ fn deref(&self) -> &R {
133+ &self.inner
134+ }
135+}
136+
137+impl<R> DerefMut for Cached<R>
138+where
139+ R: Default,
140+{
141+ fn deref_mut(&mut self) -> &mut R {
142+ &mut self.inner
143+ }
144+}
145+
146+impl<'a, R> Render<'a> for Cached<R>
147+where
148+ R: 'static + Default + for<'b> Render<'b>,
149+{
150+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
151+ let template = cx.template::<R>();
152+ let cached = match self.cached.get() {
153+ // This does-the-cache-contain-this-id check is necessary because
154+ // the same `Cached<R>` instance can be rendered into vdom A, which
155+ // will save the results into A's cached set and yield id X. Then,
156+ // it can be rendered *again* into a second vdom B, and B's cached
157+ // set does not have the saved results for id X. If we didn't have
158+ // this check, instead of a cache miss, we would have a panic.
159+ //
160+ // This scenario should basically never happen in the real world,
161+ // however. If we ever find that it is worth sharing a cached render
162+ // component between multiple vdoms, and want to avoid these
163+ // "unnecessary" cache misses, we can do this:
164+ //
165+ // * Make each `Cached<R>` have an instance id and generation
166+ // counter.
167+ //
168+ // * On invalidation, bump the generation counter.
169+ //
170+ // * Add a generation member to the `CacheEntry`.
171+ //
172+ // * Each vdom maintains a map from `Cached<R>` instance id to
173+ // `CacheEntry`s.
174+ //
175+ // * We only re-use the cached results if the `Cached<R>`'s
176+ // generation counter matches the entry in the vdom's cached set.
177+ //
178+ // This is all do-able but is a bit more book keeping than we really
179+ // want to do unless it is well motivated.
180+ Some(cached)
181+ if {
182+ let cached_set = cx.cached_set.borrow();
183+ cached_set.contains(cached.id)
184+ } =>
185+ {
186+ cached
187+ }
188+ _ => {
189+ let mut key = NodeKey::NONE;
190+ let id = CachedSet::insert(cx, false, template, |nested_cx| {
191+ let node = self.inner.render(nested_cx);
192+ key = node.key();
193+ node
194+ });
195+ let cached = CachedNode { id, key };
196+ self.cached.set(Some(cached));
197+ cached
198+ }
199+ };
200+
201+ cached.into()
202+ }
203+}
204+
205+#[cfg(feature = "serde")]
206+impl<R> serde::Serialize for Cached<R>
207+where
208+ R: Default + serde::Serialize,
209+{
210+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
211+ where
212+ S: serde::Serializer,
213+ {
214+ self.inner.serialize(serializer)
215+ }
216+}
217+
218+#[cfg(feature = "serde")]
219+impl<'de, R> serde::Deserialize<'de> for Cached<R>
220+where
221+ R: Default + serde::Deserialize<'de>,
222+{
223+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224+ where
225+ D: serde::Deserializer<'de>,
226+ {
227+ R::deserialize(deserializer).map(Cached::new)
228+ }
229+}
src/cached_set.rsnew file+186
@@ -0,0 +1,186 @@
1+use crate::{
2+ events::EventsRegistry,
3+ node::{Node, NodeKind},
4+ render_context::RenderContext,
5+};
6+use bumpalo::Bump;
7+use fxhash::{FxHashMap, FxHashSet};
8+use std::sync::atomic::{AtomicUsize, Ordering};
9+use std::u32;
10+use wasm_bindgen::prelude::*;
11+
12+static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
13+
14+pub_unstable_internal! {
15+ #[derive(Debug, Default)]
16+ pub(crate) struct CachedSet {
17+ items: FxHashMap<CacheId, CacheEntry>,
18+ }
19+}
20+
21+pub_unstable_internal! {
22+ #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
23+ pub(crate) struct CacheId(u32);
24+}
25+
26+#[derive(Debug)]
27+pub(crate) struct CacheEntry {
28+ bump: Bump,
29+
30+ // Actually a reference into `bump`.
31+ node: *const Node<'static>,
32+
33+ // All the other cached entries that `node` references (including transitive
34+ // edges). By eagerly following transitive edges and recording them the
35+ // once, we don't have to repeat the tracing every time we want to garbage
36+ // collect cache entries.
37+ edges: FxHashSet<CacheId>,
38+
39+ // The template for this cached virtual subtree.
40+ //
41+ // When a cache entry has a template, that means that the `ChangeList` will
42+ // lazily build a physical DOM subtree based on the template, and when we
43+ // create new versions of this cached result, we clone the template's
44+ // physical DOM subtree and then modify it, rather than building the cached
45+ // result up from scratch. This is a nice win because we bounce between JS
46+ // and DOM methods less often, and get the C++ DOM implementation to do most
47+ // of the subtree construction for us.
48+ template: Option<CacheId>,
49+
50+ // Whether this entry should never be garbage collected. Typically only
51+ // templates are pinned.
52+ pinned: bool,
53+}
54+
55+impl From<CacheId> for u32 {
56+ #[inline]
57+ fn from(id: CacheId) -> u32 {
58+ id.0
59+ }
60+}
61+
62+impl CachedSet {
63+ pub(crate) fn new_roots_set(&self) -> FxHashSet<CacheId> {
64+ let mut roots = FxHashSet::default();
65+ roots.reserve(self.items.len());
66+ roots
67+ }
68+
69+ pub(crate) fn gc(&mut self, registry: &mut EventsRegistry, roots: FxHashSet<CacheId>) {
70+ let mut marked = FxHashSet::default();
71+ marked.reserve(self.items.len());
72+
73+ for root in roots {
74+ if marked.insert(root) {
75+ let entry = self
76+ .items
77+ .get(&root)
78+ .expect_throw("CachedSet::gc: should have root in cached set");
79+
80+ // NB: no need to recurse here because `edges` already contains
81+ // transitive edges!
82+ marked.extend(entry.edges.iter().cloned());
83+ }
84+ }
85+
86+ self.items.retain(|id, entry| {
87+ let keep = entry.pinned || marked.contains(id);
88+ if !keep {
89+ let node: &Node = unsafe { &*entry.node };
90+ registry.remove_subtree(node);
91+ }
92+ keep
93+ });
94+ }
95+
96+ // Trace all the transitive edges to other cached entries that the given
97+ // node has.
98+ fn trace(&self, node: &Node) -> FxHashSet<CacheId> {
99+ let mut edges = FxHashSet::default();
100+ self.trace_recursive(&mut edges, node);
101+ edges
102+ }
103+
104+ fn trace_recursive(&self, edges: &mut FxHashSet<CacheId>, node: &Node) {
105+ match &node.kind {
106+ NodeKind::Text(_) => return,
107+ NodeKind::Cached(c) => {
108+ debug_assert!(self.items.contains_key(&c.id));
109+ edges.insert(c.id);
110+ edges.extend(
111+ self.items
112+ .get(&c.id)
113+ .expect_throw("CachedSet::trace: should have c.id in cached set")
114+ .edges
115+ .iter()
116+ .cloned(),
117+ );
118+ }
119+ NodeKind::Element(el) => {
120+ for child in el.children {
121+ self.trace_recursive(edges, child);
122+ }
123+ }
124+ }
125+ }
126+
127+ fn next_id(&mut self) -> CacheId {
128+ let next = ID_COUNTER.fetch_add(1, Ordering::AcqRel) as u32;
129+ let next = if next == u32::MAX { None } else { Some(next) };
130+ CacheId(next.expect_throw("ID_COUNTER overflowed"))
131+ }
132+
133+ pub(crate) fn insert<F>(
134+ cx: &mut RenderContext,
135+ pinned: bool,
136+ template: Option<CacheId>,
137+ f: F,
138+ ) -> CacheId
139+ where
140+ F: for<'a> FnOnce(&mut RenderContext<'a>) -> Node<'a>,
141+ {
142+ let set = cx.cached_set;
143+ let bump = Bump::new();
144+ let (node, edges) = {
145+ let mut nested_cx = RenderContext::new(&bump, cx.cached_set, cx.templates);
146+ let node = f(&mut nested_cx);
147+ let node = bump.alloc(node);
148+ let edges = {
149+ let set = set.borrow();
150+ set.trace(node)
151+ };
152+ (
153+ node as *mut Node<'_> as usize as *const Node<'static>,
154+ edges,
155+ )
156+ };
157+
158+ let entry = CacheEntry {
159+ bump,
160+ node,
161+ edges,
162+ template,
163+ pinned,
164+ };
165+
166+ let mut set = set.borrow_mut();
167+ let id = set.next_id();
168+ set.items.insert(id, entry);
169+ id
170+ }
171+
172+ /// Does the cached set contain a cached node with the given id?
173+ pub fn contains(&self, id: CacheId) -> bool {
174+ self.items.contains_key(&id)
175+ }
176+
177+ /// Get the cached node and its template (if any) for the given cache id.
178+ pub fn get(&self, id: CacheId) -> (&Node, Option<CacheId>) {
179+ let entry = self
180+ .items
181+ .get(&id)
182+ .expect_throw("CachedSet::get: should have id in set");
183+ let node: &Node = unsafe { &*entry.node };
184+ (node, entry.template)
185+ }
186+}
src/change_list.rsdeleted−224
@@ -1,224 +0,0 @@
1−use bumpalo::{Bump, BumpAllocSafe};
2−use wasm_bindgen::prelude::*;
3−
4−pub mod js {
5− use wasm_bindgen::prelude::*;
6−
7− #[wasm_bindgen(module = "dodrio/change-list")]
8− extern "C" {
9− #[derive(Clone, Debug)]
10− pub type ChangeList;
11−
12− #[wasm_bindgen(constructor)]
13− pub fn new(container: &web_sys::Node) -> ChangeList;
14−
15− #[wasm_bindgen(method, js_name = addChangeListRange)]
16− pub fn add_change_list_range(this: &ChangeList, start: usize, len: usize);
17−
18− #[wasm_bindgen(method, js_name = applyChanges)]
19− pub fn apply_changes(this: &ChangeList, memory: JsValue);
20− }
21−}
22−
23−pub(crate) struct ChangeList {
24− bump: Bump,
25− js: js::ChangeList,
26−}
27−
28−impl ChangeList {
29− pub(crate) fn new(container: &web_sys::Node) -> ChangeList {
30− let bump = Bump::new();
31− let js = js::ChangeList::new(container);
32− ChangeList { bump, js }
33− }
34−
35− pub(crate) fn apply_changes(&mut self) {
36− let js = &self.js;
37− unsafe {
38− self.bump.each_allocated_chunk(|ch| {
39− js.add_change_list_range(ch.as_ptr() as usize, ch.len());
40− });
41− }
42− js.apply_changes(wasm_bindgen::memory());
43− self.bump.reset();
44− }
45−}
46−
47−#[wasm_bindgen]
48−#[repr(u32)]
49−pub enum ChangeDiscriminant {
50− /// Immediates: `(pointer, length)`
51− ///
52− /// Stack: `[... TextNode] -> [... TextNode]`
53− ///
54− /// ```text
55− /// stack.top().textContent = String(pointer, length)
56− /// ```
57− SetText = 0,
58−
59− /// Immediates: `()`
60− ///
61− /// Stack: `[... Node] -> [...]`
62− ///
63− /// ```text
64− /// node = stack.pop()
65− /// while (node.nextSibling) {
66− /// node.nextSibling.remove();
67− /// }
68− /// node.remove()
69− /// ```
70− RemoveSelfAndNextSiblings = 1,
71−
72− /// Immediates: `()`
73− ///
74− /// Stack: `[... Node Node] -> [... Node]`
75− ///
76− /// ```text
77− /// new = stack.pop()
78− /// old = stack.pop()
79− /// old.replaceWith(new)
80− /// stack.push(new)
81− /// ```
82− ReplaceWith = 2,
83−
84− /// Immediates: `(pointer1, length1, pointer2, length2)`
85− ///
86− /// Stack: `[... Node] -> [... Node]`
87− ///
88− /// ```text
89− /// stack.top().setAttribute(String(pointer1, length1), String(pointer2, length2))
90− /// ```
91− SetAttribute = 3,
92−
93− /// Immediates: (pointer, length)
94− ///
95− /// Stack: `[... Node] -> [... Node]`
96− ///
97− /// ```text
98− /// stack.top().removeAttribute(String(pointer, length))
99− /// ```
100− RemoveAttribute = 4,
101−
102− /// Immediates: `()`
103− ///
104− /// Stack: `[... Node] -> [... Node node]`
105− ///
106− /// ```text
107− /// stack.push(stack.top().firstChild)
108− /// ```
109− PushFirstChild = 5,
110−
111− /// Immediates: `()`
112− ///
113− /// Stack: `[... Node] -> [... Node]`
114− ///
115− /// ```text
116− /// stack.push(stack.pop().nextSibling)
117− /// ```
118− PopPushNextSibling = 6,
119−
120− /// Immediates: `()`
121− ///
122− /// Stack: `[... T] -> [...]`
123− ///
124− /// ```text
125− /// stack.pop()
126− /// ```
127− Pop = 7,
128−
129− /// Immediates: `()`
130− ///
131− /// Stack: `[... Node Node] -> [... Node]`
132− ///
133− /// ```text
134− /// child = stack.pop()
135− /// stack.top().appendChild(child)
136− /// ```
137− AppendChild = 8,
138−
139− /// Immediates: `(pointer, length)`
140− ///
141− /// Stack: `[...] -> [... Node]`
142− ///
143− /// ```text
144− /// stack.push(createTextNode(String(pointer, length)))
145− /// ```
146− CreateTextNode = 9,
147−
148− /// Immediates: `(pointer, length)`
149− ///
150− /// Stack: `[...] -> [... Node]`
151− ///
152− /// ```text
153− /// stack.push(createElement(String(pointer, length))
154− /// ```
155− CreateElement = 10,
156−}
157−
158−impl BumpAllocSafe for ChangeDiscriminant {}
159−
160−impl ChangeList {
161− pub(crate) fn emit_set_text(&self, text: &str) {
162− self.bump
163− .alloc((ChangeDiscriminant::SetText, text.as_ptr(), text.len()));
164− }
165−
166− pub(crate) fn emit_remove_self_and_next_siblings(&self) {
167− self.bump
168− .alloc(ChangeDiscriminant::RemoveSelfAndNextSiblings);
169− }
170−
171− pub(crate) fn emit_replace_with(&self) {
172− self.bump.alloc(ChangeDiscriminant::ReplaceWith);
173− }
174−
175− pub(crate) fn emit_set_attribute(&self, name: &str, value: &str) {
176− self.bump.alloc((
177− ChangeDiscriminant::SetAttribute,
178− name.as_ptr(),
179− name.len(),
180− value.as_ptr(),
181− value.len(),
182− ));
183− }
184−
185− pub(crate) fn emit_remove_attribute(&self, name: &str) {
186− self.bump.alloc((
187− ChangeDiscriminant::RemoveAttribute,
188− name.as_ptr(),
189− name.len(),
190− ));
191− }
192−
193− pub(crate) fn emit_push_first_child(&self) {
194− self.bump.alloc(ChangeDiscriminant::PushFirstChild);
195− }
196−
197− pub(crate) fn emit_pop_push_next_sibling(&self) {
198− self.bump.alloc(ChangeDiscriminant::PopPushNextSibling);
199− }
200−
201− pub(crate) fn emit_pop(&self) {
202− self.bump.alloc(ChangeDiscriminant::Pop);
203− }
204−
205− pub(crate) fn emit_append_child(&self) {
206− self.bump.alloc(ChangeDiscriminant::AppendChild);
207− }
208−
209− pub(crate) fn emit_create_text_node(&self, text: &str) {
210− self.bump.alloc((
211− ChangeDiscriminant::CreateTextNode,
212− text.as_ptr(),
213− text.len(),
214− ));
215− }
216−
217− pub(crate) fn emit_create_element(&self, tag_name: &str) {
218− self.bump.alloc((
219− ChangeDiscriminant::CreateElement,
220− tag_name.as_ptr(),
221− tag_name.len(),
222− ));
223− }
224−}
src/change_list/emitter.rsnew file+292
@@ -0,0 +1,292 @@
1+//! The `InstructionEmitter` is responsible for encoding change list
2+//! instructions and ensuring that each instruction has the correct number of
3+//! immediates. It ensures that the resulting change list instruction stream is
4+//! *syntactically* correct (opcodes have the correct arity of immediate
5+//! arguments, etc), while `ChangeListBuilder` wraps an emitter and additionally
6+//! ensures that the resulting change list program is *semantically* correct
7+//! (doesn't reference cached strings before they've been added to the cache,
8+//! etc).
9+//!
10+//! We encode the instructions directly into a dedicated bump arena. We
11+//! eventually pass the bump arena's chunks to the interpreter in JS, so it is
12+//! critical that nothing other than change list instructions are allocated
13+//! inside this bump, and that the instructions themselves do not contain any
14+//! padding or uninitialized memory. See the documentation for the the
15+//! `Bump::each_allocated_chunk` method for details.
16+
17+use bumpalo::Bump;
18+
19+#[derive(Debug)]
20+pub(crate) struct InstructionEmitter {
21+ bump: Bump,
22+}
23+
24+impl InstructionEmitter {
25+ /// Construct a new `InstructionEmitter` with its own bump arena.
26+ pub fn new() -> InstructionEmitter {
27+ let bump = Bump::new();
28+ InstructionEmitter { bump }
29+ }
30+
31+ /// Invoke the given function with each of the allocated instruction
32+ /// sequences that this emitter has built up.
33+ #[cfg_attr(feature = "xxx-unstable-internal-use-only", allow(dead_code))]
34+ pub fn each_instruction_sequence<F>(&mut self, f: F)
35+ where
36+ F: FnMut(&[u8]),
37+ {
38+ // Note: the safety invariants required for `each_allocated_chunk` are
39+ // maintained by the fact that everything we encode as allocations in
40+ // the bump arena are in the form of `u32`s, and therefore we don't have
41+ // any uninitialized memory padding in the arena.
42+ unsafe {
43+ self.bump.each_allocated_chunk(f);
44+ }
45+ }
46+
47+ /// Reset to an empty sequence of instructions.
48+ pub fn reset(&mut self) {
49+ self.bump.reset();
50+ }
51+}
52+
53+macro_rules! define_change_list_instructions {
54+ ( $(
55+ $( #[$attr:meta] )*
56+ $name:ident (
57+ $($immediate:ident),*
58+ ) = $discriminant:expr,
59+ )* ) => {
60+ impl InstructionEmitter {
61+ $(
62+ $( #[$attr] )*
63+ #[inline]
64+ pub fn $name(&self $(, $immediate: u32)*) {
65+ self.bump.alloc_with(|| [$discriminant $(, $immediate )* ]);
66+ }
67+ )*
68+ }
69+ }
70+}
71+
72+define_change_list_instructions! {
73+ /// Stack: `[... TextNode] -> [... TextNode]`
74+ ///
75+ /// ```text
76+ /// stack.top().textContent = readString(pointer, length)
77+ /// ```
78+ set_text(pointer, length) = 0,
79+
80+ /// Stack: `[... Node] -> [...]`
81+ ///
82+ /// ```text
83+ /// node = stack.pop()
84+ /// while (node.nextSibling) {
85+ /// node.nextSibling.remove();
86+ /// }
87+ /// node.remove()
88+ /// ```
89+ remove_self_and_next_siblings() = 1,
90+
91+ /// Stack: `[... Node Node] -> [... Node]`
92+ ///
93+ /// ```text
94+ /// new = stack.pop()
95+ /// old = stack.pop()
96+ /// old.replaceWith(new)
97+ /// stack.push(new)
98+ /// ```
99+ replace_with() = 2,
100+
101+ /// Stack: `[... Node] -> [... Node]`
102+ ///
103+ /// ```text
104+ /// stack.top().setAttribute(getCachedString(attribute_key), getCachedString(value_key))
105+ /// ```
106+ set_attribute(attribute_key, value_key) = 3,
107+
108+ /// Stack: `[... Node] -> [... Node]`
109+ ///
110+ /// ```text
111+ /// stack.top().removeAttribute(getCachedString(attribute_key))
112+ /// ```
113+ remove_attribute(attribute_key) = 4,
114+
115+ /// Stack: `[... Node] -> [... Node Node]`
116+ ///
117+ /// ```text
118+ /// parent = stack.top()
119+ /// child = parent.childNodes[parent.childNodes.length - n - 1]
120+ /// stack.push(child)
121+ /// ```
122+ push_reverse_child(n) = 5,
123+
124+ /// Stack: `[... Node Node] -> [... Node Node]`
125+ ///
126+ /// ```text
127+ /// stack.pop();
128+ /// parent = stack.top();
129+ /// child = parent.childNodes[n]
130+ /// stack.push(child)
131+ /// ```
132+ pop_push_child(n) = 6,
133+
134+ /// Stack: `[... T] -> [...]`
135+ ///
136+ /// ```text
137+ /// stack.pop()
138+ /// ```
139+ pop() = 7,
140+
141+ /// Stack: `[... Node Node] -> [... Node]`
142+ ///
143+ /// ```text
144+ /// child = stack.pop()
145+ /// stack.top().appendChild(child)
146+ /// ```
147+ append_child() = 8,
148+
149+ /// Stack: `[...] -> [... Node]`
150+ ///
151+ /// ```text
152+ /// stack.push(document.createTextNode(readString(pointer, length)))
153+ /// ```
154+ create_text_node(pointer, length) = 9,
155+
156+ /// Stack: `[...] -> [... Node]`
157+ ///
158+ /// ```text
159+ /// stack.push(document.createElement(getCachedString(tag_name_key))
160+ /// ```
161+ create_element(tag_name_key) = 10,
162+
163+ /// Stack: `[... Node] -> [... Node]`
164+ ///
165+ /// ```text
166+ /// event = getCachedString(event_key)
167+ /// callback = createProxyToRustCallback(a, b)
168+ /// stack.top().addEventListener(event, callback)
169+ /// ```
170+ new_event_listener(event_key, a, b) = 11,
171+
172+ /// Stack: `[... Node] -> [... Node]`
173+ ///
174+ /// ```text
175+ /// event = getCachedString(event_key)
176+ /// new_callback = createProxyToRustCallback(a, b);
177+ /// stack.top().updateEventlistener(new_callback)
178+ /// ```
179+ update_event_listener(event_key, a, b) = 12,
180+
181+ /// Stack: `[... Node] -> [... Node]`
182+ ///
183+ /// ```text
184+ /// stack.top().removeEventListener(getCachedString(event_key));
185+ /// ```
186+ remove_event_listener(event_key) = 13,
187+
188+ /// Stack: `[...] -> [...]`
189+ ///
190+ /// ```text
191+ /// addCachedString(readString(pointer, length), key);
192+ /// ```
193+ add_cached_string(pointer, length, key) = 14,
194+
195+ /// Stack: `[...] -> [...]`
196+ ///
197+ /// ```text
198+ /// dropCachedString(key);
199+ /// ```
200+ drop_cached_string(key) = 15,
201+
202+ /// Stack: `[...] -> [... Node]`
203+ ///
204+ /// ```text
205+ /// tag_name = getCachedString(tag_name_key)
206+ /// namespace = getCachedString(tag_name_key)
207+ /// stack.push(document.createElementNS(tag_name, namespace))
208+ /// ```
209+ create_element_ns(tag_name_key, namespace_key) = 16,
210+
211+ /// Stack: `[...] -> [...]`
212+ ///
213+ /// ```text
214+ /// parent = stack.top()
215+ /// children = parent.childNodes
216+ /// temp = temp_base
217+ /// for i in start .. end:
218+ /// temporaries[temp] = children[i]
219+ /// temp += 1
220+ /// ```
221+ save_children_to_temporaries(temp_base, start, end) = 17,
222+
223+ /// Stack: `[... Node] -> [... Node Node]`
224+ ///
225+ /// ```text
226+ /// parent = stack.top()
227+ /// child = parent.childNodes[n]
228+ /// stack.push(child)
229+ /// ```
230+ push_child(n) = 18,
231+
232+ /// Stack: `[...] -> [... Node]`
233+ ///
234+ /// ```text
235+ /// stack.push(temporaries[temp])
236+ /// ```
237+ push_temporary(temp) = 19,
238+
239+ /// Stack: `[... Node Node] -> [... Node]`
240+ ///
241+ /// ```text
242+ /// before = stack.pop()
243+ /// after = stack.pop()
244+ /// after.insertBefore(before)
245+ /// stack.push(before)
246+ /// ```
247+ insert_before() = 20,
248+
249+ /// Stack: `[... Node Node] -> [... Node Node]`
250+ ///
251+ /// ```text
252+ /// stack.pop()
253+ /// parent = stack.top()
254+ /// child = parent.childNodes[parent.childNodes.length - n - 1]
255+ /// stack.push(child)
256+ /// ```
257+ pop_push_reverse_child(n) = 21,
258+
259+ /// Stack: `[... Node] -> [... Node]`
260+ ///
261+ /// ```text
262+ /// parent = stack.top()
263+ /// child = parent.childNodes[n]
264+ /// child.remove()
265+ /// ```
266+ remove_child(n) = 22,
267+
268+ /// Stack: `[... Node] -> [... Node]`
269+ ///
270+ /// ```text
271+ /// class = getCachedString(class)
272+ /// node = stack.top()
273+ /// node.className = class
274+ /// ```
275+ set_class(class) = 23,
276+
277+ /// Stack: `[... Node] -> [... Node]`
278+ ///
279+ /// ```text
280+ /// template = stack.top()
281+ /// saveTemplate(id, template)
282+ /// ```
283+ save_template(id) = 24,
284+
285+ /// Stack: `[...] -> [... Node]`
286+ ///
287+ /// ```text
288+ /// template = getTemplate(id)
289+ /// stack.push(template.cloneNode(true))
290+ /// ```
291+ push_template(id) = 25,
292+}
src/change_list/js.rsnew file+40
@@ -0,0 +1,40 @@
1+cfg_if::cfg_if! {
2+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
3+ #[derive(Clone, Debug)]
4+ pub struct ChangeListInterpreter {}
5+ impl ChangeListInterpreter {
6+ pub fn new(_container: &crate::Element) -> ChangeListInterpreter {
7+ ChangeListInterpreter {}
8+ }
9+ pub fn unmount(&self) {}
10+ pub fn add_change_list_range(&self, _start: usize, _len: usize) {}
11+ pub fn init_events_trampoline(&self, _trampoline: &crate::EventsTrampoline) {}
12+ }
13+ } else {
14+ use wasm_bindgen::prelude::*;
15+
16+ #[wasm_bindgen(module = "/js/change-list-interpreter.js")]
17+ extern "C" {
18+ #[derive(Clone, Debug)]
19+ pub type ChangeListInterpreter;
20+
21+ #[wasm_bindgen(constructor)]
22+ pub fn new(container: &web_sys::Element) -> ChangeListInterpreter;
23+
24+ #[wasm_bindgen(structural, method)]
25+ pub fn unmount(this: &ChangeListInterpreter);
26+
27+ #[wasm_bindgen(structural, method, js_name = addChangeListRange)]
28+ pub fn add_change_list_range(this: &ChangeListInterpreter, start: usize, len: usize);
29+
30+ #[wasm_bindgen(structural, method, js_name = applyChanges)]
31+ pub fn apply_changes(this: &ChangeListInterpreter, memory: JsValue);
32+
33+ #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)]
34+ pub fn init_events_trampoline(
35+ this: &ChangeListInterpreter,
36+ trampoline: &crate::EventsTrampoline,
37+ );
38+ }
39+ }
40+}
src/change_list/mod.rsnew file+353
@@ -0,0 +1,353 @@
1+pub(crate) mod emitter;
2+pub(crate) mod strings;
3+pub(crate) mod traversal;
4+
5+// Note: has to be `pub` because of `wasm-bindgen` visibility restrictions.
6+pub mod js;
7+
8+use self::emitter::InstructionEmitter;
9+use self::strings::{StringKey, StringsCache};
10+use self::traversal::{MoveTo, Traversal};
11+use crate::{cached_set::CacheId, Listener};
12+use fxhash::FxHashSet;
13+
14+#[derive(Debug)]
15+pub(crate) struct ChangeListPersistentState {
16+ strings: StringsCache,
17+ emitter: InstructionEmitter,
18+ traversal: Traversal,
19+ interpreter: js::ChangeListInterpreter,
20+ templates: FxHashSet<CacheId>,
21+}
22+
23+pub(crate) struct ChangeListBuilder<'a> {
24+ state: &'a mut ChangeListPersistentState,
25+ next_temporary: u32,
26+ forcing_new_listeners: bool,
27+}
28+
29+impl Drop for ChangeListPersistentState {
30+ fn drop(&mut self) {
31+ self.interpreter.unmount();
32+ }
33+}
34+
35+impl ChangeListPersistentState {
36+ pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState {
37+ let strings = StringsCache::new();
38+ let emitter = InstructionEmitter::new();
39+ let traversal = Traversal::new();
40+ let interpreter = js::ChangeListInterpreter::new(container);
41+ let templates = Default::default();
42+ ChangeListPersistentState {
43+ strings,
44+ emitter,
45+ traversal,
46+ interpreter,
47+ templates,
48+ }
49+ }
50+
51+ pub(crate) fn init_events_trampoline(&mut self, trampoline: &crate::EventsTrampoline) {
52+ self.interpreter.init_events_trampoline(trampoline);
53+ }
54+
55+ pub(crate) fn builder<'a>(&'a mut self) -> ChangeListBuilder<'a> {
56+ ChangeListBuilder {
57+ state: self,
58+ next_temporary: 0,
59+ forcing_new_listeners: false,
60+ }
61+ }
62+}
63+
64+cfg_if::cfg_if! {
65+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
66+ impl ChangeListBuilder<'_> {
67+ pub(crate) fn finish(self) {
68+ self.state.strings.drop_unused_strings(&self.state.emitter);
69+
70+ // Nothing to actually apply the changes to.
71+
72+ self.state.emitter.reset();
73+ self.state.traversal.reset();
74+ }
75+ }
76+ } else {
77+ impl ChangeListBuilder<'_> {
78+ pub(crate) fn finish(self) {
79+ self.state.strings.drop_unused_strings(&self.state.emitter);
80+
81+ // Apply the changes.
82+ let interpreter = &self.state.interpreter;
83+ self.state.emitter.each_instruction_sequence(|seq| {
84+ interpreter.add_change_list_range(seq.as_ptr() as usize, seq.len());
85+ });
86+ interpreter.apply_changes(wasm_bindgen::memory());
87+
88+ self.state.emitter.reset();
89+ self.state.traversal.reset();
90+ }
91+ }
92+ }
93+}
94+
95+/// Traversal methods.
96+impl ChangeListBuilder<'_> {
97+ pub fn go_down(&mut self) {
98+ self.state.traversal.down();
99+ }
100+
101+ pub fn go_down_to_child(&mut self, index: usize) {
102+ self.state.traversal.down();
103+ self.state.traversal.sibling(index);
104+ }
105+
106+ pub fn go_down_to_reverse_child(&mut self, index: usize) {
107+ self.state.traversal.down();
108+ self.state.traversal.reverse_sibling(index);
109+ }
110+
111+ pub fn go_up(&mut self) {
112+ self.state.traversal.up();
113+ }
114+
115+ pub fn go_to_sibling(&mut self, index: usize) {
116+ self.state.traversal.sibling(index);
117+ }
118+
119+ pub fn go_to_temp_sibling(&mut self, temp: u32) {
120+ self.state.traversal.up();
121+ self.state.traversal.down_to_temp(temp);
122+ }
123+
124+ pub fn go_down_to_temp_child(&mut self, temp: u32) {
125+ self.state.traversal.down_to_temp(temp);
126+ }
127+
128+ pub fn commit_traversal(&mut self) {
129+ if self.state.traversal.is_committed() {
130+ return;
131+ }
132+
133+ for mv in self.state.traversal.commit() {
134+ match mv {
135+ MoveTo::Parent => {
136+ debug!("emit: pop");
137+ self.state.emitter.pop();
138+ }
139+ MoveTo::Child(n) => {
140+ debug!("emit: push_child({})", n);
141+ self.state.emitter.push_child(n);
142+ }
143+ MoveTo::ReverseChild(n) => {
144+ debug!("emit: push_reverse_child({})", n);
145+ self.state.emitter.push_reverse_child(n);
146+ }
147+ MoveTo::Sibling(n) => {
148+ debug!("emit: pop_push_child({})", n);
149+ self.state.emitter.pop_push_child(n);
150+ }
151+ MoveTo::ReverseSibling(n) => {
152+ debug!("emit: pop_push_reverse_child({})", n);
153+ self.state.emitter.pop_push_reverse_child(n);
154+ }
155+ MoveTo::TempChild(temp) => {
156+ debug!("emit: push_temporary({})", temp);
157+ self.state.emitter.push_temporary(temp);
158+ }
159+ }
160+ }
161+ }
162+
163+ pub fn traversal_is_committed(&self) -> bool {
164+ self.state.traversal.is_committed()
165+ }
166+}
167+
168+impl ChangeListBuilder<'_> {
169+ pub fn next_temporary(&self) -> u32 {
170+ self.next_temporary
171+ }
172+
173+ pub fn set_next_temporary(&mut self, next_temporary: u32) {
174+ self.next_temporary = next_temporary;
175+ }
176+
177+ pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
178+ debug_assert!(self.traversal_is_committed());
179+ debug_assert!(start < end);
180+ let temp_base = self.next_temporary;
181+ debug!(
182+ "emit: save_children_to_temporaries({}, {}, {})",
183+ temp_base, start, end
184+ );
185+ self.next_temporary = temp_base + (end - start) as u32;
186+ self.state
187+ .emitter
188+ .save_children_to_temporaries(temp_base, start as u32, end as u32);
189+ temp_base
190+ }
191+
192+ pub fn push_temporary(&self, temp: u32) {
193+ debug_assert!(self.traversal_is_committed());
194+ debug!("emit: push_temporary({})", temp);
195+ self.state.emitter.push_temporary(temp);
196+ }
197+
198+ pub fn remove_child(&self, child: usize) {
199+ debug_assert!(self.traversal_is_committed());
200+ debug!("emit: remove_child({})", child);
201+ self.state.emitter.remove_child(child as u32);
202+ }
203+
204+ pub fn insert_before(&self) {
205+ debug_assert!(self.traversal_is_committed());
206+ debug!("emit: insert_before()");
207+ self.state.emitter.insert_before();
208+ }
209+
210+ pub fn ensure_string(&mut self, string: &str) -> StringKey {
211+ self.state
212+ .strings
213+ .ensure_string(string, &self.state.emitter)
214+ }
215+
216+ pub fn set_text(&self, text: &str) {
217+ debug_assert!(self.traversal_is_committed());
218+ debug!("emit: set_text({:?})", text);
219+ self.state
220+ .emitter
221+ .set_text(text.as_ptr() as u32, text.len() as u32);
222+ }
223+
224+ pub fn remove_self_and_next_siblings(&self) {
225+ debug_assert!(self.traversal_is_committed());
226+ debug!("emit: remove_self_and_next_siblings()");
227+ self.state.emitter.remove_self_and_next_siblings();
228+ }
229+
230+ pub fn replace_with(&self) {
231+ debug_assert!(self.traversal_is_committed());
232+ debug!("emit: replace_with()");
233+ self.state.emitter.replace_with();
234+ }
235+
236+ pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
237+ debug_assert!(self.traversal_is_committed());
238+ if name == "class" && !is_namespaced {
239+ let class_id = self.ensure_string(value);
240+ debug!("emit: set_class({:?})", value);
241+ self.state.emitter.set_class(class_id.into());
242+ } else {
243+ let name_id = self.ensure_string(name);
244+ let value_id = self.ensure_string(value);
245+ debug!("emit: set_attribute({:?}, {:?})", name, value);
246+ self.state
247+ .emitter
248+ .set_attribute(name_id.into(), value_id.into());
249+ }
250+ }
251+
252+ pub fn remove_attribute(&mut self, name: &str) {
253+ debug_assert!(self.traversal_is_committed());
254+ debug!("emit: remove_attribute({:?})", name);
255+ let name_id = self.ensure_string(name);
256+ self.state.emitter.remove_attribute(name_id.into());
257+ }
258+
259+ pub fn append_child(&self) {
260+ debug_assert!(self.traversal_is_committed());
261+ debug!("emit: append_child()");
262+ self.state.emitter.append_child();
263+ }
264+
265+ pub fn create_text_node(&self, text: &str) {
266+ debug_assert!(self.traversal_is_committed());
267+ debug!("emit: create_text_node({:?})", text);
268+ self.state
269+ .emitter
270+ .create_text_node(text.as_ptr() as u32, text.len() as u32);
271+ }
272+
273+ pub fn create_element(&mut self, tag_name: &str) {
274+ debug_assert!(self.traversal_is_committed());
275+ debug!("emit: create_element({:?})", tag_name);
276+ let tag_name_id = self.ensure_string(tag_name);
277+ self.state.emitter.create_element(tag_name_id.into());
278+ }
279+
280+ pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
281+ debug_assert!(self.traversal_is_committed());
282+ debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
283+ let tag_name_id = self.ensure_string(tag_name);
284+ let ns_id = self.ensure_string(ns);
285+ self.state
286+ .emitter
287+ .create_element_ns(tag_name_id.into(), ns_id.into());
288+ }
289+
290+ pub fn push_force_new_listeners(&mut self) -> bool {
291+ let old = self.forcing_new_listeners;
292+ self.forcing_new_listeners = true;
293+ old
294+ }
295+
296+ pub fn pop_force_new_listeners(&mut self, previous: bool) {
297+ debug_assert!(self.forcing_new_listeners);
298+ self.forcing_new_listeners = previous;
299+ }
300+
301+ pub fn new_event_listener(&mut self, listener: &Listener) {
302+ debug_assert!(self.traversal_is_committed());
303+ debug!("emit: new_event_listener({:?})", listener);
304+ let (a, b) = listener.get_callback_parts();
305+ debug_assert!(a != 0);
306+ let event_id = self.ensure_string(listener.event);
307+ self.state.emitter.new_event_listener(event_id.into(), a, b);
308+ }
309+
310+ pub fn update_event_listener(&mut self, listener: &Listener) {
311+ debug_assert!(self.traversal_is_committed());
312+
313+ if self.forcing_new_listeners {
314+ self.new_event_listener(listener);
315+ return;
316+ }
317+
318+ debug!("emit: update_event_listener({:?})", listener);
319+ let (a, b) = listener.get_callback_parts();
320+ debug_assert!(a != 0);
321+ let event_id = self.ensure_string(listener.event);
322+ self.state
323+ .emitter
324+ .update_event_listener(event_id.into(), a, b);
325+ }
326+
327+ pub fn remove_event_listener(&mut self, event: &str) {
328+ debug_assert!(self.traversal_is_committed());
329+ debug!("emit: remove_event_listener({:?})", event);
330+ let event_id = self.ensure_string(event);
331+ self.state.emitter.remove_event_listener(event_id.into());
332+ }
333+
334+ #[inline]
335+ pub fn has_template(&mut self, id: CacheId) -> bool {
336+ self.state.templates.contains(&id)
337+ }
338+
339+ pub fn save_template(&mut self, id: CacheId) {
340+ debug_assert!(self.traversal_is_committed());
341+ debug_assert!(!self.has_template(id));
342+ debug!("emit: save_template({:?})", id);
343+ self.state.templates.insert(id);
344+ self.state.emitter.save_template(id.into());
345+ }
346+
347+ pub fn push_template(&mut self, id: CacheId) {
348+ debug_assert!(self.traversal_is_committed());
349+ debug_assert!(self.has_template(id));
350+ debug!("emit: push_template({:?})", id);
351+ self.state.emitter.push_template(id.into());
352+ }
353+}
src/change_list/strings.rsnew file+66
@@ -0,0 +1,66 @@
1+use crate::change_list::emitter::InstructionEmitter;
2+use fxhash::FxHashMap;
3+
4+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5+pub struct StringKey(u32);
6+
7+impl From<StringKey> for u32 {
8+ #[inline]
9+ fn from(key: StringKey) -> u32 {
10+ key.0
11+ }
12+}
13+
14+#[derive(Debug)]
15+struct StringsCacheEntry {
16+ key: StringKey,
17+ used: bool,
18+}
19+
20+#[derive(Debug, Default)]
21+pub(crate) struct StringsCache {
22+ entries: FxHashMap<String, StringsCacheEntry>,
23+ next_string_key: u32,
24+}
25+
26+impl StringsCache {
27+ /// Create a new, empty strings cache.
28+ pub fn new() -> StringsCache {
29+ Default::default()
30+ }
31+
32+ /// Ensure that the given string is cached, and get its key.
33+ pub fn ensure_string(&mut self, string: &str, emitter: &InstructionEmitter) -> StringKey {
34+ if let Some(entry) = self.entries.get_mut(string) {
35+ entry.used = true;
36+ entry.key
37+ } else {
38+ let key = StringKey(self.next_string_key);
39+ self.next_string_key += 1;
40+ let entry = StringsCacheEntry { key, used: true };
41+ self.entries.insert(string.to_string(), entry);
42+ emitter.add_cached_string(string.as_ptr() as u32, string.len() as u32, key.into());
43+ key
44+ }
45+ }
46+
47+ pub fn drop_unused_strings(&mut self, emitter: &InstructionEmitter) {
48+ self.entries.retain(|string, entry| {
49+ if entry.used {
50+ // Since this entry was used during while rendering this frame,
51+ // it is likely going to be used for the next frame as well. So
52+ // we keep it in the cache so that we don't have to repopulate
53+ // and resync the cache next frame (assuming it is reused), but
54+ // we set the `used` flag to false so that if it is not used
55+ // next frame, it will be cleaned up.
56+ entry.used = false;
57+ true
58+ } else {
59+ let key = entry.key.into();
60+ debug!("emit: drop_cached_string({}) = {:?}", key, string);
61+ emitter.drop_cached_string(key);
62+ false
63+ }
64+ });
65+ }
66+}
src/change_list/traversal.rsnew file+344
@@ -0,0 +1,344 @@
1+//! Keeps track of where we are moving in a DOM tree, and shortens traversal
2+//! paths between mutations to their minimal number of operations.
3+
4+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5+pub enum MoveTo {
6+ /// Move from the current node up to its parent.
7+ Parent,
8+
9+ /// Move to the current node's n^th child.
10+ Child(u32),
11+
12+ /// Move to the current node's n^th from last child.
13+ ReverseChild(u32),
14+
15+ /// Move to the n^th sibling. Not relative from the current
16+ /// location. Absolute indexed within all of the current siblings.
17+ Sibling(u32),
18+
19+ /// Move to the n^th from last sibling. Not relative from the current
20+ /// location. Absolute indexed within all of the current siblings.
21+ ReverseSibling(u32),
22+
23+ /// Move down to the given saved temporary child.
24+ TempChild(u32),
25+}
26+
27+#[derive(Debug)]
28+pub struct Traversal {
29+ uncommitted: Vec<MoveTo>,
30+}
31+
32+impl Traversal {
33+ /// Construct a new `Traversal` with its internal storage backed by the
34+ /// given bump arena.
35+ pub fn new() -> Traversal {
36+ Traversal {
37+ uncommitted: Vec::with_capacity(32),
38+ }
39+ }
40+
41+ /// Move the traversal up in the tree.
42+ pub fn up(&mut self) {
43+ match self.uncommitted.last() {
44+ Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
45+ self.uncommitted.pop();
46+ self.uncommitted.push(MoveTo::Parent);
47+ }
48+ Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
49+ self.uncommitted.pop();
50+ // And we're back at the parent.
51+ }
52+ _ => {
53+ self.uncommitted.push(MoveTo::Parent);
54+ }
55+ }
56+ }
57+
58+ /// Move the traversal down in the tree to the first child of the current
59+ /// node.
60+ pub fn down(&mut self) {
61+ if let Some(&MoveTo::Parent) = self.uncommitted.last() {
62+ self.uncommitted.pop();
63+ self.sibling(0);
64+ } else {
65+ self.uncommitted.push(MoveTo::Child(0));
66+ }
67+ }
68+
69+ /// Move the traversal to the n^th sibling.
70+ pub fn sibling(&mut self, index: usize) {
71+ let index = index as u32;
72+ match self.uncommitted.last_mut() {
73+ Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
74+ *n = index;
75+ }
76+ Some(MoveTo::ReverseSibling(_)) => {
77+ self.uncommitted.pop();
78+ self.uncommitted.push(MoveTo::Sibling(index));
79+ }
80+ Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
81+ self.uncommitted.pop();
82+ self.uncommitted.push(MoveTo::Child(index))
83+ }
84+ _ => {
85+ self.uncommitted.push(MoveTo::Sibling(index));
86+ }
87+ }
88+ }
89+
90+ /// Move the the n^th from last sibling.
91+ pub fn reverse_sibling(&mut self, index: usize) {
92+ let index = index as u32;
93+ match self.uncommitted.last_mut() {
94+ Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
95+ *n = index;
96+ }
97+ Some(MoveTo::Sibling(_)) => {
98+ self.uncommitted.pop();
99+ self.uncommitted.push(MoveTo::ReverseSibling(index));
100+ }
101+ Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
102+ self.uncommitted.pop();
103+ self.uncommitted.push(MoveTo::ReverseChild(index))
104+ }
105+ _ => {
106+ self.uncommitted.push(MoveTo::ReverseSibling(index));
107+ }
108+ }
109+ }
110+
111+ /// Go to the given saved temporary.
112+ pub fn down_to_temp(&mut self, temp: u32) {
113+ match self.uncommitted.last() {
114+ Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
115+ self.uncommitted.pop();
116+ }
117+ Some(MoveTo::Parent)
118+ | Some(MoveTo::TempChild(_))
119+ | Some(MoveTo::Child(_))
120+ | Some(MoveTo::ReverseChild(_))
121+ | None => {
122+ // Can't remove moves to parents since we rely on their stack
123+ // pops.
124+ }
125+ }
126+ self.uncommitted.push(MoveTo::TempChild(temp));
127+ }
128+
129+ /// Are all the traversal's moves committed? That is, are there no moves
130+ /// that have *not* been committed yet?
131+ #[inline]
132+ pub fn is_committed(&self) -> bool {
133+ self.uncommitted.is_empty()
134+ }
135+
136+ /// Commit this traversals moves and return the optimized path from the last
137+ /// commit.
138+ #[inline]
139+ pub fn commit(&mut self) -> Moves {
140+ Moves {
141+ inner: self.uncommitted.drain(..),
142+ }
143+ }
144+
145+ #[inline]
146+ pub fn reset(&mut self) {
147+ self.uncommitted.clear();
148+ }
149+}
150+
151+pub struct Moves<'a> {
152+ inner: std::vec::Drain<'a, MoveTo>,
153+}
154+
155+impl Iterator for Moves<'_> {
156+ type Item = MoveTo;
157+
158+ #[inline]
159+ fn next(&mut self) -> Option<MoveTo> {
160+ self.inner.next()
161+ }
162+}
163+
164+#[cfg(test)]
165+mod tests {
166+ use super::*;
167+
168+ #[test]
169+ fn test_traversal() {
170+ fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
171+ where
172+ F: 'static + FnMut(&mut Traversal),
173+ {
174+ Box::new(f) as _
175+ }
176+
177+ for (mut traverse, expected_moves) in vec![
178+ (
179+ t(|t| {
180+ t.down();
181+ }),
182+ vec![MoveTo::Child(0)],
183+ ),
184+ (
185+ t(|t| {
186+ t.up();
187+ }),
188+ vec![MoveTo::Parent],
189+ ),
190+ (
191+ t(|t| {
192+ t.sibling(42);
193+ }),
194+ vec![MoveTo::Sibling(42)],
195+ ),
196+ (
197+ t(|t| {
198+ t.down();
199+ t.up();
200+ }),
201+ vec![],
202+ ),
203+ (
204+ t(|t| {
205+ t.down();
206+ t.sibling(2);
207+ t.up();
208+ }),
209+ vec![],
210+ ),
211+ (
212+ t(|t| {
213+ t.down();
214+ t.sibling(3);
215+ }),
216+ vec![MoveTo::Child(3)],
217+ ),
218+ (
219+ t(|t| {
220+ t.down();
221+ t.sibling(4);
222+ t.sibling(8);
223+ }),
224+ vec![MoveTo::Child(8)],
225+ ),
226+ (
227+ t(|t| {
228+ t.sibling(1);
229+ t.sibling(1);
230+ }),
231+ vec![MoveTo::Sibling(1)],
232+ ),
233+ (
234+ t(|t| {
235+ t.reverse_sibling(3);
236+ }),
237+ vec![MoveTo::ReverseSibling(3)],
238+ ),
239+ (
240+ t(|t| {
241+ t.down();
242+ t.reverse_sibling(3);
243+ }),
244+ vec![MoveTo::ReverseChild(3)],
245+ ),
246+ (
247+ t(|t| {
248+ t.down();
249+ t.reverse_sibling(3);
250+ t.up();
251+ }),
252+ vec![],
253+ ),
254+ (
255+ t(|t| {
256+ t.down();
257+ t.reverse_sibling(3);
258+ t.reverse_sibling(6);
259+ }),
260+ vec![MoveTo::ReverseChild(6)],
261+ ),
262+ (
263+ t(|t| {
264+ t.up();
265+ t.reverse_sibling(3);
266+ t.reverse_sibling(6);
267+ }),
268+ vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
269+ ),
270+ (
271+ t(|t| {
272+ t.up();
273+ t.sibling(3);
274+ t.sibling(6);
275+ }),
276+ vec![MoveTo::Parent, MoveTo::Sibling(6)],
277+ ),
278+ (
279+ t(|t| {
280+ t.sibling(3);
281+ t.sibling(6);
282+ t.up();
283+ }),
284+ vec![MoveTo::Parent],
285+ ),
286+ (
287+ t(|t| {
288+ t.reverse_sibling(3);
289+ t.reverse_sibling(6);
290+ t.up();
291+ }),
292+ vec![MoveTo::Parent],
293+ ),
294+ (
295+ t(|t| {
296+ t.down();
297+ t.down_to_temp(3);
298+ }),
299+ vec![MoveTo::Child(0), MoveTo::TempChild(3)],
300+ ),
301+ (
302+ t(|t| {
303+ t.down_to_temp(3);
304+ t.sibling(5);
305+ }),
306+ vec![MoveTo::Child(5)],
307+ ),
308+ (
309+ t(|t| {
310+ t.down_to_temp(3);
311+ t.reverse_sibling(5);
312+ }),
313+ vec![MoveTo::ReverseChild(5)],
314+ ),
315+ (
316+ t(|t| {
317+ t.down_to_temp(3);
318+ t.up();
319+ }),
320+ vec![],
321+ ),
322+ (
323+ t(|t| {
324+ t.sibling(2);
325+ t.up();
326+ t.down_to_temp(3);
327+ }),
328+ vec![MoveTo::Parent, MoveTo::TempChild(3)],
329+ ),
330+ (
331+ t(|t| {
332+ t.up();
333+ t.down_to_temp(3);
334+ }),
335+ vec![MoveTo::Parent, MoveTo::TempChild(3)],
336+ ),
337+ ] {
338+ let mut traversal = Traversal::new();
339+ traverse(&mut traversal);
340+ let actual_moves: Vec<_> = traversal.commit().collect();
341+ assert_eq!(actual_moves, expected_moves);
342+ }
343+ }
344+}
src/diff.rsnew file+1125
@@ -0,0 +1,1125 @@
1+use crate::{
2+ cached_set::{CacheId, CachedSet},
3+ change_list::ChangeListBuilder,
4+ events::EventsRegistry,
5+ node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
6+};
7+use fxhash::{FxHashMap, FxHashSet};
8+use std::cmp::Ordering;
9+use std::u32;
10+use wasm_bindgen::UnwrapThrowExt;
11+
12+// Diff the `old` node with the `new` node. Emits instructions to modify a
13+// physical DOM node that reflects `old` into something that reflects `new`.
14+//
15+// Upon entry to this function, the physical DOM node must be on the top of the
16+// change list stack:
17+//
18+// [... node]
19+//
20+// The change list stack is in the same state when this function exits.
21+pub(crate) fn diff(
22+ cached_set: &CachedSet,
23+ change_list: &mut ChangeListBuilder,
24+ registry: &mut EventsRegistry,
25+ old: &Node,
26+ new: &Node,
27+ cached_roots: &mut FxHashSet<CacheId>,
28+) {
29+ match (&new.kind, &old.kind) {
30+ (
31+ &NodeKind::Text(TextNode { text: new_text }),
32+ &NodeKind::Text(TextNode { text: old_text }),
33+ ) => {
34+ if new_text != old_text {
35+ change_list.commit_traversal();
36+ change_list.set_text(new_text);
37+ }
38+ }
39+
40+ (&NodeKind::Text(_), &NodeKind::Element(_)) => {
41+ change_list.commit_traversal();
42+ create(cached_set, change_list, registry, new, cached_roots);
43+ registry.remove_subtree(&old);
44+ change_list.replace_with();
45+ }
46+
47+ (&NodeKind::Element(_), &NodeKind::Text(_)) => {
48+ change_list.commit_traversal();
49+ create(cached_set, change_list, registry, new, cached_roots);
50+ // Note: text nodes cannot have event listeners, so we don't need to
51+ // remove the old node's listeners from our registry her.
52+ change_list.replace_with();
53+ }
54+
55+ (
56+ &NodeKind::Element(ElementNode {
57+ key: _,
58+ tag_name: new_tag_name,
59+ listeners: new_listeners,
60+ attributes: new_attributes,
61+ children: new_children,
62+ namespace: new_namespace,
63+ }),
64+ &NodeKind::Element(ElementNode {
65+ key: _,
66+ tag_name: old_tag_name,
67+ listeners: old_listeners,
68+ attributes: old_attributes,
69+ children: old_children,
70+ namespace: old_namespace,
71+ }),
72+ ) => {
73+ if new_tag_name != old_tag_name || new_namespace != old_namespace {
74+ change_list.commit_traversal();
75+ create(cached_set, change_list, registry, new, cached_roots);
76+ registry.remove_subtree(&old);
77+ change_list.replace_with();
78+ return;
79+ }
80+ diff_listeners(change_list, registry, old_listeners, new_listeners);
81+ diff_attributes(change_list, old_attributes, new_attributes, new_namespace.is_some());
82+ diff_children(
83+ cached_set,
84+ change_list,
85+ registry,
86+ old_children,
87+ new_children,
88+ cached_roots,
89+ );
90+ }
91+
92+ // Both the new and old nodes are cached.
93+ (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
94+ cached_roots.insert(new.id);
95+
96+ if new.id == old.id {
97+ // This is the same cached node, so nothing has changed!
98+ return;
99+ }
100+
101+ let (new, new_template) = cached_set.get(new.id);
102+ let (old, old_template) = cached_set.get(old.id);
103+ if new_template == old_template {
104+ // If they are both using the same template, then just diff the
105+ // subtrees.
106+ diff(cached_set, change_list, registry, old, new, cached_roots);
107+ } else {
108+ // Otherwise, they are probably different enough that
109+ // re-constructing the subtree from scratch should be faster.
110+ // This doubly holds true if we have a new template.
111+ change_list.commit_traversal();
112+ create_and_replace(
113+ cached_set,
114+ change_list,
115+ registry,
116+ new_template,
117+ old,
118+ new,
119+ cached_roots,
120+ );
121+ }
122+ }
123+
124+ // New cached node when the old node was not cached. In this scenario,
125+ // we assume that they are pretty different, and it isn't worth diffing
126+ // the subtrees, so we just create the new cached node afresh.
127+ (&NodeKind::Cached(ref c), _) => {
128+ change_list.commit_traversal();
129+ cached_roots.insert(c.id);
130+ let (new, new_template) = cached_set.get(c.id);
131+ create_and_replace(
132+ cached_set,
133+ change_list,
134+ registry,
135+ new_template,
136+ old,
137+ new,
138+ cached_roots,
139+ );
140+ }
141+
142+ // Old cached node and new non-cached node. Again, assume that they are
143+ // probably pretty different and create the new non-cached node afresh.
144+ (_, &NodeKind::Cached(_)) => {
145+ change_list.commit_traversal();
146+ create(cached_set, change_list, registry, new, cached_roots);
147+ registry.remove_subtree(&old);
148+ change_list.replace_with();
149+ }
150+ }
151+}
152+
153+// Diff event listeners between `old` and `new`.
154+//
155+// The listeners' node must be on top of the change list stack:
156+//
157+// [... node]
158+//
159+// The change list stack is left unchanged.
160+fn diff_listeners(
161+ change_list: &mut ChangeListBuilder,
162+ registry: &mut EventsRegistry,
163+ old: &[Listener],
164+ new: &[Listener],
165+) {
166+ if !old.is_empty() || !new.is_empty() {
167+ change_list.commit_traversal();
168+ }
169+
170+ 'outer1: for new_l in new {
171+ unsafe {
172+ // Safety relies on removing `new_l` from the registry manually at
173+ // the end of its lifetime. This happens below in the `'outer2`
174+ // loop, and elsewhere in diffing when removing old dom trees.
175+ registry.add(new_l);
176+ }
177+
178+ for old_l in old {
179+ if new_l.event == old_l.event {
180+ change_list.update_event_listener(new_l);
181+ continue 'outer1;
182+ }
183+ }
184+
185+ change_list.new_event_listener(new_l);
186+ }
187+
188+ 'outer2: for old_l in old {
189+ registry.remove(old_l);
190+
191+ for new_l in new {
192+ if new_l.event == old_l.event {
193+ continue 'outer2;
194+ }
195+ }
196+ change_list.remove_event_listener(old_l.event);
197+ }
198+}
199+
200+// Diff a node's attributes.
201+//
202+// The attributes' node must be on top of the change list stack:
203+//
204+// [... node]
205+//
206+// The change list stack is left unchanged.
207+fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute], is_namespaced: bool) {
208+ // Do O(n^2) passes to add/update and remove attributes, since
209+ // there are almost always very few attributes.
210+ 'outer: for new_attr in new {
211+ if new_attr.is_volatile() {
212+ change_list.commit_traversal();
213+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
214+ } else {
215+ for old_attr in old {
216+ if old_attr.name == new_attr.name {
217+ if old_attr.value != new_attr.value {
218+ change_list.commit_traversal();
219+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
220+ }
221+ continue 'outer;
222+ }
223+ }
224+
225+ change_list.commit_traversal();
226+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
227+ }
228+ }
229+
230+ 'outer2: for old_attr in old {
231+ for new_attr in new {
232+ if old_attr.name == new_attr.name {
233+ continue 'outer2;
234+ }
235+ }
236+
237+ change_list.commit_traversal();
238+ change_list.remove_attribute(old_attr.name);
239+ }
240+}
241+
242+// Diff the given set of old and new children.
243+//
244+// The parent must be on top of the change list stack when this function is
245+// entered:
246+//
247+// [... parent]
248+//
249+// the change list stack is in the same state when this function returns.
250+fn diff_children(
251+ cached_set: &CachedSet,
252+ change_list: &mut ChangeListBuilder,
253+ registry: &mut EventsRegistry,
254+ old: &[Node],
255+ new: &[Node],
256+ cached_roots: &mut FxHashSet<CacheId>,
257+) {
258+ if new.is_empty() {
259+ if !old.is_empty() {
260+ change_list.commit_traversal();
261+ remove_all_children(change_list, registry, old);
262+ }
263+ return;
264+ }
265+
266+ if new.len() == 1 {
267+ match (old.first(), &new[0]) {
268+ (
269+ Some(&Node {
270+ kind: NodeKind::Text(TextNode { text: old_text }),
271+ }),
272+ &Node {
273+ kind: NodeKind::Text(TextNode { text: new_text }),
274+ },
275+ ) if old_text == new_text => {
276+ // Don't take this fast path...
277+ }
278+ (
279+ _,
280+ &Node {
281+ kind: NodeKind::Text(TextNode { text }),
282+ },
283+ ) => {
284+ change_list.commit_traversal();
285+ change_list.set_text(text);
286+ for o in old {
287+ registry.remove_subtree(o);
288+ }
289+ return;
290+ }
291+ (_, _) => {}
292+ }
293+ }
294+
295+ if old.is_empty() {
296+ if !new.is_empty() {
297+ change_list.commit_traversal();
298+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
299+ }
300+ return;
301+ }
302+
303+ let new_is_keyed = new[0].key().is_some();
304+ let old_is_keyed = old[0].key().is_some();
305+
306+ debug_assert!(
307+ new.iter().all(|n| n.key().is_some() == new_is_keyed),
308+ "all siblings must be keyed or all siblings must be non-keyed"
309+ );
310+ debug_assert!(
311+ old.iter().all(|o| o.key().is_some() == old_is_keyed),
312+ "all siblings must be keyed or all siblings must be non-keyed"
313+ );
314+
315+ if new_is_keyed && old_is_keyed {
316+ let t = change_list.next_temporary();
317+ diff_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
318+ change_list.set_next_temporary(t);
319+ } else {
320+ diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
321+ }
322+}
323+
324+// Diffing "keyed" children.
325+//
326+// With keyed children, we care about whether we delete, move, or create nodes
327+// versus mutate existing nodes in place. Presumably there is some sort of CSS
328+// transition animation that makes the virtual DOM diffing algorithm
329+// observable. By specifying keys for nodes, we know which virtual DOM nodes
330+// must reuse (or not reuse) the same physical DOM nodes.
331+//
332+// This is loosely based on Inferno's keyed patching implementation. However, we
333+// have to modify the algorithm since we are compiling the diff down into change
334+// list instructions that will be executed later, rather than applying the
335+// changes to the DOM directly as we compare virtual DOMs.
336+//
337+// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
338+//
339+// When entering this function, the parent must be on top of the change list
340+// stack:
341+//
342+// [... parent]
343+//
344+// Upon exiting, the change list stack is in the same state.
345+fn diff_keyed_children(
346+ cached_set: &CachedSet,
347+ change_list: &mut ChangeListBuilder,
348+ registry: &mut EventsRegistry,
349+ old: &[Node],
350+ new: &[Node],
351+ cached_roots: &mut FxHashSet<CacheId>,
352+) {
353+ if cfg!(debug_assertions) {
354+ let mut keys = FxHashSet::default();
355+ let mut assert_unique_keys = |children: &[Node]| {
356+ keys.clear();
357+ for child in children {
358+ let key = child.key();
359+ debug_assert!(
360+ key.is_some(),
361+ "if any sibling is keyed, all siblings must be keyed"
362+ );
363+ keys.insert(key);
364+ }
365+ debug_assert_eq!(
366+ children.len(),
367+ keys.len(),
368+ "keyed siblings must each have a unique key"
369+ );
370+ };
371+ assert_unique_keys(old);
372+ assert_unique_keys(new);
373+ }
374+
375+ // First up, we diff all the nodes with the same key at the beginning of the
376+ // children.
377+ //
378+ // `shared_prefix_count` is the count of how many nodes at the start of
379+ // `new` and `old` share the same keys.
380+ let shared_prefix_count =
381+ match diff_keyed_prefix(cached_set, change_list, registry, old, new, cached_roots) {
382+ KeyedPrefixResult::Finished => return,
383+ KeyedPrefixResult::MoreWorkToDo(count) => count,
384+ };
385+
386+ // Next, we find out how many of the nodes at the end of the children have
387+ // the same key. We do _not_ diff them yet, since we want to emit the change
388+ // list instructions such that they can be applied in a single pass over the
389+ // DOM. Instead, we just save this information for later.
390+ //
391+ // `shared_suffix_count` is the count of how many nodes at the end of `new`
392+ // and `old` share the same keys.
393+ let shared_suffix_count = old[shared_prefix_count..]
394+ .iter()
395+ .rev()
396+ .zip(new[shared_prefix_count..].iter().rev())
397+ .take_while(|&(old, new)| old.key() == new.key())
398+ .count();
399+
400+ let old_shared_suffix_start = old.len() - shared_suffix_count;
401+ let new_shared_suffix_start = new.len() - shared_suffix_count;
402+
403+ // Ok, we now hopefully have a smaller range of children in the middle
404+ // within which to re-order nodes with the same keys, remove old nodes with
405+ // now-unused keys, and create new nodes with fresh keys.
406+ diff_keyed_middle(
407+ cached_set,
408+ change_list,
409+ registry,
410+ &old[shared_prefix_count..old_shared_suffix_start],
411+ &new[shared_prefix_count..new_shared_suffix_start],
412+ cached_roots,
413+ shared_prefix_count,
414+ shared_suffix_count,
415+ old_shared_suffix_start,
416+ );
417+
418+ // Finally, diff the nodes at the end of `old` and `new` that share keys.
419+ let old_suffix = &old[old_shared_suffix_start..];
420+ let new_suffix = &new[new_shared_suffix_start..];
421+ debug_assert_eq!(old_suffix.len(), new_suffix.len());
422+ if !old_suffix.is_empty() {
423+ diff_keyed_suffix(
424+ cached_set,
425+ change_list,
426+ registry,
427+ old_suffix,
428+ new_suffix,
429+ cached_roots,
430+ new_shared_suffix_start,
431+ );
432+ }
433+}
434+
435+enum KeyedPrefixResult {
436+ // Fast path: we finished diffing all the children just by looking at the
437+ // prefix of shared keys!
438+ Finished,
439+ // There is more diffing work to do. Here is a count of how many children at
440+ // the beginning of `new` and `old` we already processed.
441+ MoreWorkToDo(usize),
442+}
443+
444+// Diff the prefix of children in `new` and `old` that share the same keys in
445+// the same order.
446+//
447+// Upon entry of this function, the change list stack must be:
448+//
449+// [... parent]
450+//
451+// Upon exit, the change list stack is the same.
452+fn diff_keyed_prefix(
453+ cached_set: &CachedSet,
454+ change_list: &mut ChangeListBuilder,
455+ registry: &mut EventsRegistry,
456+ old: &[Node],
457+ new: &[Node],
458+ cached_roots: &mut FxHashSet<CacheId>,
459+) -> KeyedPrefixResult {
460+ change_list.go_down();
461+ let mut shared_prefix_count = 0;
462+
463+ for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
464+ if old.key() != new.key() {
465+ break;
466+ }
467+
468+ change_list.go_to_sibling(i);
469+ diff(cached_set, change_list, registry, old, new, cached_roots);
470+ shared_prefix_count += 1;
471+ }
472+
473+ // If that was all of the old children, then create and append the remaining
474+ // new children and we're finished.
475+ if shared_prefix_count == old.len() {
476+ change_list.go_up();
477+ change_list.commit_traversal();
478+ create_and_append_children(
479+ cached_set,
480+ change_list,
481+ registry,
482+ &new[shared_prefix_count..],
483+ cached_roots,
484+ );
485+ return KeyedPrefixResult::Finished;
486+ }
487+
488+ // And if that was all of the new children, then remove all of the remaining
489+ // old children and we're finished.
490+ if shared_prefix_count == new.len() {
491+ change_list.go_to_sibling(shared_prefix_count);
492+ change_list.commit_traversal();
493+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
494+ return KeyedPrefixResult::Finished;
495+ }
496+
497+ change_list.go_up();
498+ KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
499+}
500+
501+// The most-general, expensive code path for keyed children diffing.
502+//
503+// We find the longest subsequence within `old` of children that are relatively
504+// ordered the same way in `new` (via finding a longest-increasing-subsequence
505+// of the old child's index within `new`). The children that are elements of
506+// this subsequence will remain in place, minimizing the number of DOM moves we
507+// will have to do.
508+//
509+// Upon entry to this function, the change list stack must be:
510+//
511+// [... parent]
512+//
513+// Upon exit from this function, it will be restored to that same state.
514+fn diff_keyed_middle(
515+ cached_set: &CachedSet,
516+ change_list: &mut ChangeListBuilder,
517+ registry: &mut EventsRegistry,
518+ old: &[Node],
519+ mut new: &[Node],
520+ cached_roots: &mut FxHashSet<CacheId>,
521+ shared_prefix_count: usize,
522+ shared_suffix_count: usize,
523+ old_shared_suffix_start: usize,
524+) {
525+ // Should have already diffed the shared-key prefixes and suffixes.
526+ debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
527+ debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
528+
529+ // The algorithm below relies upon using `u32::MAX` as a sentinel
530+ // value, so if we have that many new nodes, it won't work. This
531+ // check is a bit academic (hence only enabled in debug), since
532+ // wasm32 doesn't have enough address space to hold that many nodes
533+ // in memory.
534+ debug_assert!(new.len() < u32::MAX as usize);
535+
536+ // Map from each `old` node's key to its index within `old`.
537+ let mut old_key_to_old_index = FxHashMap::default();
538+ old_key_to_old_index.reserve(old.len());
539+ old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
540+
541+ // The set of shared keys between `new` and `old`.
542+ let mut shared_keys = FxHashSet::default();
543+ // Map from each index in `new` to the index of the node in `old` that
544+ // has the same key.
545+ let mut new_index_to_old_index = Vec::with_capacity(new.len());
546+ new_index_to_old_index.extend(new.iter().map(|n| {
547+ let key = n.key();
548+ if let Some(&i) = old_key_to_old_index.get(&key) {
549+ shared_keys.insert(key);
550+ i
551+ } else {
552+ u32::MAX as usize
553+ }
554+ }));
555+
556+ // If none of the old keys are reused by the new children, then we
557+ // remove all the remaining old children and create the new children
558+ // afresh.
559+ if shared_suffix_count == 0 && shared_keys.is_empty() {
560+ if shared_prefix_count == 0 {
561+ change_list.commit_traversal();
562+ remove_all_children(change_list, registry, old);
563+ } else {
564+ change_list.go_down_to_child(shared_prefix_count);
565+ change_list.commit_traversal();
566+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
567+ }
568+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
569+ return;
570+ }
571+
572+ // Save each of the old children whose keys are reused in the new
573+ // children.
574+ let mut old_index_to_temp = vec![u32::MAX; old.len()];
575+ let mut start = 0;
576+ loop {
577+ let end = (start..old.len())
578+ .find(|&i| {
579+ let key = old[i].key();
580+ !shared_keys.contains(&key)
581+ })
582+ .unwrap_or(old.len());
583+
584+ if end - start > 0 {
585+ change_list.commit_traversal();
586+ let mut t = change_list.save_children_to_temporaries(
587+ shared_prefix_count + start,
588+ shared_prefix_count + end,
589+ );
590+ for i in start..end {
591+ old_index_to_temp[i] = t;
592+ t += 1;
593+ }
594+ }
595+
596+ debug_assert!(end <= old.len());
597+ if end == old.len() {
598+ break;
599+ } else {
600+ start = end + 1;
601+ }
602+ }
603+
604+ // Remove any old children whose keys were not reused in the new
605+ // children. Remove from the end first so that we don't mess up indices.
606+ let mut removed_count = 0;
607+ for (i, old_child) in old.iter().enumerate().rev() {
608+ if !shared_keys.contains(&old_child.key()) {
609+ registry.remove_subtree(old_child);
610+ change_list.commit_traversal();
611+ change_list.remove_child(i + shared_prefix_count);
612+ removed_count += 1;
613+ }
614+ }
615+
616+ // If there aren't any more new children, then we are done!
617+ if new.is_empty() {
618+ return;
619+ }
620+
621+ // The longest increasing subsequence within `new_index_to_old_index`. This
622+ // is the longest sequence on DOM nodes in `old` that are relatively ordered
623+ // correctly within `new`. We will leave these nodes in place in the DOM,
624+ // and only move nodes that are not part of the LIS. This results in the
625+ // maximum number of DOM nodes left in place, AKA the minimum number of DOM
626+ // nodes moved.
627+ let mut new_index_is_in_lis = FxHashSet::default();
628+ new_index_is_in_lis.reserve(new_index_to_old_index.len());
629+ let mut predecessors = vec![0; new_index_to_old_index.len()];
630+ let mut starts = vec![0; new_index_to_old_index.len()];
631+ longest_increasing_subsequence::lis_with(
632+ &new_index_to_old_index,
633+ &mut new_index_is_in_lis,
634+ |a, b| a < b,
635+ &mut predecessors,
636+ &mut starts,
637+ );
638+
639+ // Now we will iterate from the end of the new children back to the
640+ // beginning, diffing old children we are reusing and if they aren't in the
641+ // LIS moving them to their new destination, or creating new children. Note
642+ // that iterating in reverse order lets us use `Node.prototype.insertBefore`
643+ // to move/insert children.
644+ //
645+ // But first, we ensure that we have a child on the change list stack that
646+ // we can `insertBefore`. We handle this once before looping over `new`
647+ // children, so that we don't have to keep checking on every loop iteration.
648+ if shared_suffix_count > 0 {
649+ // There is a shared suffix after these middle children. We will be
650+ // inserting before that shared suffix, so add the first child of that
651+ // shared suffix to the change list stack.
652+ //
653+ // [... parent]
654+ change_list.go_down_to_child(old_shared_suffix_start - removed_count);
655+ // [... parent first_child_of_shared_suffix]
656+ } else {
657+ // There is no shared suffix coming after these middle children.
658+ // Therefore we have to process the last child in `new` and move it to
659+ // the end of the parent's children if it isn't already there.
660+ let last_index = new.len() - 1;
661+ let last = new.last().unwrap_throw();
662+ new = &new[..new.len() - 1];
663+ if shared_keys.contains(&last.key()) {
664+ let old_index = new_index_to_old_index[last_index];
665+ let temp = old_index_to_temp[old_index];
666+ // [... parent]
667+ change_list.go_down_to_temp_child(temp);
668+ // [... parent last]
669+ diff(
670+ cached_set,
671+ change_list,
672+ registry,
673+ &old[old_index],
674+ last,
675+ cached_roots,
676+ );
677+ if new_index_is_in_lis.contains(&last_index) {
678+ // Don't move it, since it is already where it needs to be.
679+ } else {
680+ change_list.commit_traversal();
681+ // [... parent last]
682+ change_list.append_child();
683+ // [... parent]
684+ change_list.go_down_to_temp_child(temp);
685+ // [... parent last]
686+ }
687+ } else {
688+ change_list.commit_traversal();
689+ // [... parent]
690+ create(cached_set, change_list, registry, last, cached_roots);
691+ // [... parent last]
692+ change_list.append_child();
693+ // [... parent]
694+ change_list.go_down_to_reverse_child(0);
695+ // [... parent last]
696+ }
697+ }
698+
699+ for (new_index, new_child) in new.iter().enumerate().rev() {
700+ let old_index = new_index_to_old_index[new_index];
701+ if old_index == u32::MAX as usize {
702+ debug_assert!(!shared_keys.contains(&new_child.key()));
703+ change_list.commit_traversal();
704+ // [... parent successor]
705+ create(cached_set, change_list, registry, new_child, cached_roots);
706+ // [... parent successor new_child]
707+ change_list.insert_before();
708+ // [... parent new_child]
709+ } else {
710+ debug_assert!(shared_keys.contains(&new_child.key()));
711+ let temp = old_index_to_temp[old_index];
712+ debug_assert_ne!(temp, u32::MAX);
713+
714+ if new_index_is_in_lis.contains(&new_index) {
715+ // [... parent successor]
716+ change_list.go_to_temp_sibling(temp);
717+ // [... parent new_child]
718+ } else {
719+ change_list.commit_traversal();
720+ // [... parent successor]
721+ change_list.push_temporary(temp);
722+ // [... parent successor new_child]
723+ change_list.insert_before();
724+ // [... parent new_child]
725+ }
726+
727+ diff(
728+ cached_set,
729+ change_list,
730+ registry,
731+ &old[old_index],
732+ new_child,
733+ cached_roots,
734+ );
735+ }
736+ }
737+
738+ // [... parent child]
739+ change_list.go_up();
740+ // [... parent]
741+}
742+
743+// Diff the suffix of keyed children that share the same keys in the same order.
744+//
745+// The parent must be on the change list stack when we enter this function:
746+//
747+// [... parent]
748+//
749+// When this function exits, the change list stack remains the same.
750+fn diff_keyed_suffix(
751+ cached_set: &CachedSet,
752+ change_list: &mut ChangeListBuilder,
753+ registry: &mut EventsRegistry,
754+ old: &[Node],
755+ new: &[Node],
756+ cached_roots: &mut FxHashSet<CacheId>,
757+ new_shared_suffix_start: usize,
758+) {
759+ debug_assert_eq!(old.len(), new.len());
760+ debug_assert!(!old.is_empty());
761+
762+ // [... parent]
763+ change_list.go_down();
764+ // [... parent new_child]
765+
766+ for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
767+ change_list.go_to_sibling(new_shared_suffix_start + i);
768+ diff(
769+ cached_set,
770+ change_list,
771+ registry,
772+ old_child,
773+ new_child,
774+ cached_roots,
775+ );
776+ }
777+
778+ // [... parent]
779+ change_list.go_up();
780+}
781+
782+// Diff children that are not keyed.
783+//
784+// The parent must be on the top of the change list stack when entering this
785+// function:
786+//
787+// [... parent]
788+//
789+// the change list stack is in the same state when this function returns.
790+fn diff_non_keyed_children(
791+ cached_set: &CachedSet,
792+ change_list: &mut ChangeListBuilder,
793+ registry: &mut EventsRegistry,
794+ old: &[Node],
795+ new: &[Node],
796+ cached_roots: &mut FxHashSet<CacheId>,
797+) {
798+ // Handled these cases in `diff_children` before calling this function.
799+ debug_assert!(!new.is_empty());
800+ debug_assert!(!old.is_empty());
801+
802+ // [... parent]
803+ change_list.go_down();
804+ // [... parent child]
805+
806+ for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
807+ // [... parent prev_child]
808+ change_list.go_to_sibling(i);
809+ // [... parent this_child]
810+ diff(
811+ cached_set,
812+ change_list,
813+ registry,
814+ old_child,
815+ new_child,
816+ cached_roots,
817+ );
818+ }
819+
820+ match old.len().cmp(&new.len()) {
821+ Ordering::Greater => {
822+ // [... parent prev_child]
823+ change_list.go_to_sibling(new.len());
824+ // [... parent first_child_to_remove]
825+ change_list.commit_traversal();
826+ remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
827+ // [... parent]
828+ }
829+ Ordering::Less => {
830+ // [... parent last_child]
831+ change_list.go_up();
832+ // [... parent]
833+ change_list.commit_traversal();
834+ create_and_append_children(
835+ cached_set,
836+ change_list,
837+ registry,
838+ &new[old.len()..],
839+ cached_roots,
840+ );
841+ }
842+ Ordering::Equal => {
843+ // [... parent child]
844+ change_list.go_up();
845+ // [... parent]
846+ }
847+ }
848+}
849+
850+// Create the given children and append them to the parent node.
851+//
852+// The parent node must currently be on top of the change list stack:
853+//
854+// [... parent]
855+//
856+// When this function returns, the change list stack is in the same state.
857+fn create_and_append_children(
858+ cached_set: &CachedSet,
859+ change_list: &mut ChangeListBuilder,
860+ registry: &mut EventsRegistry,
861+ new: &[Node],
862+ cached_roots: &mut FxHashSet<CacheId>,
863+) {
864+ debug_assert!(change_list.traversal_is_committed());
865+ for child in new {
866+ create(cached_set, change_list, registry, child, cached_roots);
867+ change_list.append_child();
868+ }
869+}
870+
871+// Remove all of a node's children.
872+//
873+// The change list stack must have this shape upon entry to this function:
874+//
875+// [... parent]
876+//
877+// When this function returns, the change list stack is in the same state.
878+fn remove_all_children(
879+ change_list: &mut ChangeListBuilder,
880+ registry: &mut EventsRegistry,
881+ old: &[Node],
882+) {
883+ debug_assert!(change_list.traversal_is_committed());
884+ for child in old {
885+ registry.remove_subtree(child);
886+ }
887+ // Fast way to remove all children: set the node's textContent to an empty
888+ // string.
889+ change_list.set_text("");
890+}
891+
892+// Remove the current child and all of its following siblings.
893+//
894+// The change list stack must have this shape upon entry to this function:
895+//
896+// [... parent child]
897+//
898+// After the function returns, the child is no longer on the change list stack:
899+//
900+// [... parent]
901+fn remove_self_and_next_siblings(
902+ change_list: &mut ChangeListBuilder,
903+ registry: &mut EventsRegistry,
904+ old: &[Node],
905+) {
906+ debug_assert!(change_list.traversal_is_committed());
907+ for child in old {
908+ registry.remove_subtree(child);
909+ }
910+ change_list.remove_self_and_next_siblings();
911+}
912+
913+// Emit instructions to create the given virtual node.
914+//
915+// The change list stack may have any shape upon entering this function:
916+//
917+// [...]
918+//
919+// When this function returns, the new node is on top of the change list stack:
920+//
921+// [... node]
922+fn create(
923+ cached_set: &CachedSet,
924+ change_list: &mut ChangeListBuilder,
925+ registry: &mut EventsRegistry,
926+ node: &Node,
927+ cached_roots: &mut FxHashSet<CacheId>,
928+) {
929+ debug_assert!(change_list.traversal_is_committed());
930+ match node.kind {
931+ NodeKind::Text(TextNode { text }) => {
932+ change_list.create_text_node(text);
933+ }
934+ NodeKind::Element(&ElementNode {
935+ key: _,
936+ tag_name,
937+ listeners,
938+ attributes,
939+ children,
940+ namespace,
941+ }) => {
942+ if let Some(namespace) = namespace {
943+ change_list.create_element_ns(tag_name, namespace);
944+ } else {
945+ change_list.create_element(tag_name);
946+ }
947+
948+ for l in listeners {
949+ unsafe {
950+ registry.add(l);
951+ }
952+ change_list.new_event_listener(l);
953+ }
954+
955+ for attr in attributes {
956+ change_list.set_attribute(&attr.name, &attr.value, namespace.is_some());
957+ }
958+
959+ // Fast path: if there is a single text child, it is faster to
960+ // create-and-append the text node all at once via setting the
961+ // parent's `textContent` in a single change list instruction than
962+ // to emit three instructions to (1) create a text node, (2) set its
963+ // text content, and finally (3) append the text node to this
964+ // parent.
965+ if children.len() == 1 {
966+ if let Node {
967+ kind: NodeKind::Text(TextNode { text }),
968+ } = children[0]
969+ {
970+ change_list.set_text(text);
971+ return;
972+ }
973+ }
974+
975+ for child in children {
976+ create(cached_set, change_list, registry, child, cached_roots);
977+ change_list.append_child();
978+ }
979+ }
980+ NodeKind::Cached(ref c) => {
981+ cached_roots.insert(c.id);
982+ let (node, template) = cached_set.get(c.id);
983+ if let Some(template) = template {
984+ create_with_template(
985+ cached_set,
986+ change_list,
987+ registry,
988+ template,
989+ node,
990+ cached_roots,
991+ );
992+ } else {
993+ create(cached_set, change_list, registry, node, cached_roots);
994+ }
995+ }
996+ }
997+}
998+
999+// Get or create the template.
1000+//
1001+// Upon entering this function the change list stack may be in any shape:
1002+//
1003+// [...]
1004+//
1005+// When this function returns, it leaves a freshly cloned copy of the template
1006+// on the top of the change list stack:
1007+//
1008+// [... template]
1009+#[inline]
1010+fn get_or_create_template<'a>(
1011+ cached_set: &'a CachedSet,
1012+ change_list: &mut ChangeListBuilder,
1013+ registry: &mut EventsRegistry,
1014+ cached_roots: &mut FxHashSet<CacheId>,
1015+ template_id: CacheId,
1016+) -> (&'a Node<'a>, bool) {
1017+ let (template, template_template) = cached_set.get(template_id);
1018+ debug_assert!(
1019+ template_template.is_none(),
1020+ "templates should not be templated themselves"
1021+ );
1022+
1023+ // If we haven't already created and saved the physical DOM subtree for this
1024+ // template, do that now.
1025+ if change_list.has_template(template_id) {
1026+ // Clone the template and push it onto the stack.
1027+ //
1028+ // [...]
1029+ change_list.push_template(template_id);
1030+ // [... template]
1031+
1032+ (template, true)
1033+ } else {
1034+ // [...]
1035+ create(cached_set, change_list, registry, template, cached_roots);
1036+ // [... template]
1037+ change_list.save_template(template_id);
1038+ // [... template]
1039+
1040+ (template, false)
1041+ }
1042+}
1043+
1044+fn create_and_replace(
1045+ cached_set: &CachedSet,
1046+ change_list: &mut ChangeListBuilder,
1047+ registry: &mut EventsRegistry,
1048+ new_template: Option<CacheId>,
1049+ old: &Node,
1050+ new: &Node,
1051+ cached_roots: &mut FxHashSet<CacheId>,
1052+) {
1053+ debug_assert!(change_list.traversal_is_committed());
1054+
1055+ if let Some(template_id) = new_template {
1056+ let (template, needs_listeners) =
1057+ get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
1058+ change_list.replace_with();
1059+
1060+ let mut old_forcing = None;
1061+ if needs_listeners {
1062+ old_forcing = Some(change_list.push_force_new_listeners());
1063+ }
1064+
1065+ diff(
1066+ cached_set,
1067+ change_list,
1068+ registry,
1069+ template,
1070+ new,
1071+ cached_roots,
1072+ );
1073+
1074+ if let Some(old) = old_forcing {
1075+ change_list.pop_force_new_listeners(old);
1076+ }
1077+
1078+ change_list.commit_traversal();
1079+ } else {
1080+ create(cached_set, change_list, registry, new, cached_roots);
1081+ change_list.replace_with();
1082+ }
1083+ registry.remove_subtree(old);
1084+}
1085+
1086+fn create_with_template(
1087+ cached_set: &CachedSet,
1088+ change_list: &mut ChangeListBuilder,
1089+ registry: &mut EventsRegistry,
1090+ template_id: CacheId,
1091+ node: &Node,
1092+ cached_roots: &mut FxHashSet<CacheId>,
1093+) {
1094+ debug_assert!(change_list.traversal_is_committed());
1095+
1096+ // [...]
1097+ let (template, needs_listeners) =
1098+ get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
1099+ // [... template]
1100+
1101+ // Now diff the node with its template.
1102+ //
1103+ // We must force adding new listeners instead of updating existing ones,
1104+ // since listeners don't get cloned in `cloneNode`.
1105+ let mut old_forcing = None;
1106+ if needs_listeners {
1107+ old_forcing = Some(change_list.push_force_new_listeners());
1108+ }
1109+
1110+ diff(
1111+ cached_set,
1112+ change_list,
1113+ registry,
1114+ template,
1115+ node,
1116+ cached_roots,
1117+ );
1118+
1119+ if let Some(old) = old_forcing {
1120+ change_list.pop_force_new_listeners(old);
1121+ }
1122+
1123+ // Make sure that we come back up to the level we were at originally.
1124+ change_list.commit_traversal();
1125+}
src/events.rsnew file+138
@@ -0,0 +1,138 @@
1+use crate::{node::Node, vdom::VdomInner, Listener};
2+use std::cell::RefCell;
3+use std::rc::{Rc, Weak};
4+
5+cfg_if::cfg_if! {
6+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
7+ #[derive(Debug)]
8+ pub(crate) struct EventsRegistry {}
9+ impl EventsRegistry {
10+ pub(crate) fn new(_vdom: Weak<VdomInner>) -> (
11+ Rc<RefCell<EventsRegistry>>,
12+ crate::EventsTrampoline,
13+ ) {
14+ (Rc::new(RefCell::new(EventsRegistry {})), ())
15+ }
16+ pub(crate) fn remove(&mut self, _listener: &Listener) {}
17+ pub(crate) fn remove_subtree(&mut self, _node: &Node) {}
18+ pub(crate) unsafe fn add<'a>(&mut self, _listener: &'a Listener<'a>) {}
19+ pub(crate) fn clear_active_listeners(&mut self) {}
20+ }
21+ } else {
22+ use crate::{
23+ node::{ElementNode, ListenerCallback, NodeKind},
24+ vdom::VdomWeak,
25+ };
26+ use fxhash::FxHashMap;
27+ use std::fmt;
28+ use std::mem;
29+ use wasm_bindgen::closure::Closure;
30+ use wasm_bindgen::prelude::*;
31+
32+ /// The events registry manages event listeners for a virtual DOM.
33+ ///
34+ /// The events registry is persistent across virtual DOM rendering and double
35+ /// buffering.
36+ pub(crate) struct EventsRegistry {
37+ vdom: Weak<VdomInner>,
38+ active: FxHashMap<(u32, u32), ListenerCallback<'static>>,
39+ }
40+
41+ impl fmt::Debug for EventsRegistry {
42+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43+ f.debug_struct("EventsRegistry")
44+ .field("active", &self.active.keys().collect::<Vec<_>>())
45+ .finish()
46+ }
47+ }
48+
49+ impl EventsRegistry {
50+ /// Construct a new events registry and JS function trampoline that weakly
51+ /// holds the new registry and can be used by JS to invoke listeners on the
52+ /// Rust side.
53+ pub(crate) fn new(
54+ vdom: Weak<VdomInner>,
55+ ) -> (
56+ Rc<RefCell<EventsRegistry>>,
57+ crate::EventsTrampoline,
58+ ) {
59+ let registry = Rc::new(RefCell::new(EventsRegistry {
60+ vdom,
61+ active: FxHashMap::default(),
62+ }));
63+
64+ let weak_registry = Rc::downgrade(&registry);
65+ let closure = Closure::wrap(Box::new(move |event, a, b| {
66+ debug_assert!(a != 0);
67+
68+ // if the VdomInnerExclusive is keeping this closure alive, then the
69+ // VdomInnerExclusive should also be keeping the registry alive
70+ let registry = weak_registry.upgrade().unwrap_throw();
71+ let registry = registry.borrow();
72+
73+ match registry.active.get(&(a, b)) {
74+ None => warn!(
75+ "EventsRegistry closure invoked with unknown listener parts: \
76+ (0x{:x}, 0x{:x})",
77+ a, b
78+ ),
79+ Some(callback) => {
80+ let vdom = registry.vdom.upgrade().expect_throw(
81+ "if the registry is still around, then the vdom should still be around",
82+ );
83+ let vdom_weak = VdomWeak::new(&vdom);
84+ let mut vdom = vdom.exclusive.borrow_mut();
85+ let component = vdom.component_raw_mut();
86+ callback(component, vdom_weak, event);
87+ }
88+ }
89+ }) as Box<dyn Fn(web_sys::Event, u32, u32)>);
90+
91+ (registry, closure)
92+ }
93+
94+ pub(crate) fn remove(&mut self, listener: &Listener) {
95+ let id = listener.get_callback_parts();
96+ debug_assert!(id.0 != 0);
97+ self.active.remove(&id);
98+ }
99+
100+ pub(crate) fn remove_subtree(&mut self, node: &Node) {
101+ match node.kind {
102+ NodeKind::Cached(_) | NodeKind::Text(_) => {},
103+ NodeKind::Element(&ElementNode {listeners, children, ..}) => {
104+ for l in listeners {
105+ self.remove(l);
106+ }
107+ for child in children {
108+ self.remove_subtree(child)
109+ }
110+ }
111+ }
112+ }
113+
114+ /// Add an event listener to the registry, exposing to JS.
115+ ///
116+ /// # Unsafety
117+ ///
118+ /// The listener's lifetime is extended to `'static` and it is the
119+ /// caller's responsibility to ensure that the listener is not kept
120+ /// in the registry after it is dropped. This is maintained during
121+ /// diffing.
122+ pub(crate) unsafe fn add<'a>(&mut self, listener: &'a Listener<'a>) {
123+ let id = listener.get_callback_parts();
124+ debug_assert!(id.0 != 0);
125+
126+ let callback =
127+ mem::transmute::<ListenerCallback<'a>, ListenerCallback<'static>>(listener.callback);
128+ let old = self.active.insert(id, callback);
129+ debug_assert!(old.is_none());
130+ }
131+
132+ /// Clear all event listeners from the registry.
133+ pub(crate) fn clear_active_listeners(&mut self) {
134+ self.active.clear();
135+ }
136+ }
137+ }
138+}
src/lib.rs+130−9
@@ -1,14 +1,135 @@
1−use bumpalo::Bump;
1+//! The `dodrio` virtual DOM.
2+//!
3+//! ## Example
4+//!
5+//! ```no_run
6+//! use dodrio::{bumpalo, Attribute, Node, Render, RenderContext};
7+//! use wasm_bindgen::UnwrapThrowExt;
8+//!
9+//! /// A component that greets someone.
10+//! pub struct Hello<'who> {
11+//! who: &'who str,
12+//! }
13+//!
14+//! impl<'who> Hello<'who> {
15+//! /// Construct a new `Hello` component that greets the given `who`.
16+//! pub fn new(who: &str) -> Hello {
17+//! Hello { who }
18+//! }
19+//! }
20+//!
21+//! impl<'a, 'who> Render<'a> for Hello<'who> {
22+//! fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
23+//! use dodrio::builder::*;
24+//!
25+//! let id = bumpalo::format!(in cx.bump, "hello-{}", self.who);
26+//! let who = bumpalo::collections::String::from_str_in(self.who, cx.bump).into_bump_str();
27+//!
28+//! div(&cx)
29+//! .attr("id", id.into_bump_str())
30+//! .on("click", |root, _vdom, _event| {
31+//! let hello = root.unwrap_mut::<Hello>();
32+//! web_sys::window()
33+//! .expect_throw("should have a `Window` on the Web")
34+//! .alert_with_message(hello.who);
35+//! })
36+//! .children([
37+//! text("Hello, "),
38+//! strong(&cx)
39+//! .children([
40+//! text(who),
41+//! text("!"),
42+//! ])
43+//! .finish(),
44+//! ])
45+//! .finish()
46+//! }
47+//! }
48+//! ```
49+#![deny(missing_docs, missing_debug_implementations)]
250
51+// Re-export the `bumpalo` crate.
52+pub use bumpalo;
53+
54+cfg_if::cfg_if! {
55+ if #[cfg(feature = "log")] {
56+ #[macro_use]
57+ extern crate log;
58+ } else {
59+ #[macro_use]
60+ mod logging;
61+ }
62+}
63+
64+// A macro to expose items unstably, only for internal/testing usage.
65+cfg_if::cfg_if! {
66+ if #[cfg(feature = "xxx-unstable-internal-use-only")] {
67+ macro_rules! pub_unstable_internal {
68+ ( $(#[$attr:meta])* pub(crate) $( $thing:tt )* ) => {
69+ #[doc(hidden)]
70+ $( #[$attr] )*
71+ pub $( $thing )*
72+ }
73+ }
74+ } else {
75+ macro_rules! pub_unstable_internal {
76+ ( $(#[$attr:meta])* pub(crate) $( $thing:tt )* ) => {
77+ $( #[$attr] )*
78+ pub(crate) $( $thing )*
79+ }
80+ }
81+ }
82+}
83+
84+// Only `pub` so that the wasm-bindgen bindings work.
85+#[doc(hidden)]
386pub mod change_list;
4−pub mod node;
5−pub mod vdom;
687
7−pub use self::node::{Node, NodeRef};
8−pub use self::vdom::Vdom;
88+mod cached;
89+mod cached_set;
90+mod diff;
91+mod events;
92+mod node;
93+mod render;
94+mod render_context;
95+mod strace;
96+mod vdom;
97+
98+pub mod builder;
99+
100+// Re-export items at the top level.
101+pub use self::cached::Cached;
102+pub use self::node::{Attribute, Listener, Node, NodeKey};
103+pub use self::render::{Render, RootRender};
104+pub use self::render_context::RenderContext;
105+pub use self::vdom::{Vdom, VdomWeak};
106+
107+cfg_if::cfg_if! {
108+ if #[cfg(all(target_arch = "wasm32", not(feature = "xxx-unstable-internal-use-only")))] {
109+ use wasm_bindgen::__rt::WasmRefCell as RefCell;
110+ } else {
111+ use std::cell::RefCell;
112+ }
113+}
114+
115+// Polyfill some Web stuff for benchmarking...
116+cfg_if::cfg_if! {
117+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
118+ /// An element node in the physical DOM.
119+ pub type Element = ();
120+
121+ pub(crate) type EventsTrampoline = ();
122+ } else {
123+ /// An element node in the physical DOM.
124+ pub type Element = web_sys::Element;
125+
126+ pub(crate) type EventsTrampoline = wasm_bindgen::closure::Closure<dyn Fn(web_sys::Event, u32, u32)>;
127+ }
128+}
9129
10−pub trait Render {
11− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> NodeRef<'bump>
12− where
13− 'a: 'bump;
130+cfg_if::cfg_if! {
131+ if #[cfg(feature = "xxx-unstable-internal-use-only")] {
132+ pub use self::cached_set::{CachedSet};
133+ pub use self::node::{ElementNode, NodeKind, TextNode};
134+ }
14135}
src/logging.rsnew file+65
@@ -0,0 +1,65 @@
1+#[allow(unused_macros)]
2+macro_rules! debug {
3+ ( $( $e:expr ),* $(,)* ) => {
4+ if false {
5+ $(
6+ let _ = $e;
7+ )*
8+ }
9+ }
10+}
11+
12+#[allow(unused_macros)]
13+macro_rules! error {
14+ ( $( $e:expr ),* $(,)* ) => {
15+ if false {
16+ $(
17+ let _ = $e;
18+ )*
19+ }
20+ }
21+}
22+
23+#[allow(unused_macros)]
24+macro_rules! info {
25+ ( $( $e:expr ),* $(,)* ) => {
26+ if false {
27+ $(
28+ let _ = $e;
29+ )*
30+ }
31+ }
32+}
33+
34+#[allow(unused_macros)]
35+macro_rules! log {
36+ ( $( $e:expr ),* $(,)* ) => {
37+ if false {
38+ $(
39+ let _ = $e;
40+ )*
41+ }
42+ }
43+}
44+
45+#[allow(unused_macros)]
46+macro_rules! trace {
47+ ( $( $e:expr ),* $(,)* ) => {
48+ if false {
49+ $(
50+ let _ = $e;
51+ )*
52+ }
53+ }
54+}
55+
56+#[allow(unused_macros)]
57+macro_rules! warn {
58+ ( $( $e:expr ),* $(,)* ) => {
59+ if false {
60+ $(
61+ let _ = $e;
62+ )*
63+ }
64+ }
65+}
src/node.rs+243−66
@@ -1,94 +1,271 @@
1−use bumpalo::BumpAllocSafe;
1+use crate::{cached_set::CacheId, RootRender, VdomWeak};
2+use bumpalo::Bump;
3+use std::fmt;
4+use std::iter;
5+use std::mem;
6+use std::u32;
27
3−#[derive(Clone, Debug)]
4−pub struct Node<'a, Attributes, Children> {
5− data: NodeData<'a>,
6− attributes: Attributes,
7− children: Children,
8+/// A virtual DOM node.
9+#[derive(Debug, Clone)]
10+pub struct Node<'a> {
11+ #[cfg(feature = "xxx-unstable-internal-use-only")]
12+ #[doc(hidden)]
13+ pub kind: NodeKind<'a>,
14+
15+ #[cfg(not(feature = "xxx-unstable-internal-use-only"))]
16+ pub(crate) kind: NodeKind<'a>,
817}
918
10−#[derive(Clone, Copy, Debug)]
11−pub enum NodeData<'a> {
12− Element { tag_name: &'a str },
13− Text { text: &'a str },
19+pub_unstable_internal! {
20+ /// A node is either a text node or an element.
21+ #[derive(Debug, Clone)]
22+ pub(crate) enum NodeKind<'a> {
23+ /// A text node.
24+ Text(TextNode<'a>),
25+
26+ /// An element potentially with attributes and children.
27+ Element(&'a ElementNode<'a>),
28+
29+ /// A node in the vdom's `CachedSet`. This allows us to avoid
30+ /// re-rendering and re-diffing subtrees.
31+ Cached(CachedNode),
32+ }
1433}
1534
16−#[derive(Clone, Debug)]
17−pub struct NodeRef<'a> {
18− pub(crate) data: NodeData<'a>,
19− pub(crate) attributes: &'a [Attribute<'a>],
20− pub(crate) children: &'a [NodeRef<'a>],
35+pub_unstable_internal! {
36+ /// Text nodes are just a string of text. They cannot have attributes or
37+ /// children.
38+ #[derive(Debug, Clone)]
39+ pub(crate) struct TextNode<'a> {
40+ pub text: &'a str,
41+ }
2142}
2243
44+pub_unstable_internal! {
45+ /// Elements have a tag name, zero or more attributes, and zero or more
46+ /// children.
47+ #[derive(Debug, Clone)]
48+ pub(crate) struct ElementNode<'a> {
49+ pub key: NodeKey,
50+ pub tag_name: &'a str,
51+ pub listeners: &'a [Listener<'a>],
52+ pub attributes: &'a [Attribute<'a>],
53+ pub children: &'a [Node<'a>],
54+ pub namespace: Option<&'a str>,
55+ }
56+}
57+
58+pub_unstable_internal! {
59+ /// A cached node exists in an arena that is internal to the `Vdom`. It
60+ /// allows us to avoid both re-rendering a sub-tree and re-diffing
61+ /// it. `CachedNode`s cannot contain cycles, since they can only contain
62+ /// other `CachedNode`s that already exist when they are being constructed.
63+ #[derive(Debug, Clone, Copy)]
64+ pub(crate) struct CachedNode {
65+ pub id: CacheId,
66+ // Always a copy of the inner cached node's key.
67+ pub key: NodeKey,
68+ }
69+}
70+
71+/// The key for keyed children.
72+///
73+/// Keys must be unique among siblings.
74+///
75+/// If any sibling is keyed, then they all must be keyed.
76+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
77+pub struct NodeKey(pub(crate) u32);
78+
79+impl Default for NodeKey {
80+ fn default() -> NodeKey {
81+ NodeKey::NONE
82+ }
83+}
84+
85+impl NodeKey {
86+ /// The default, lack of a key.
87+ pub const NONE: NodeKey = NodeKey(u32::MAX);
88+
89+ /// Is this key `NodeKey::NONE`?
90+ #[inline]
91+ pub fn is_none(&self) -> bool {
92+ *self == Self::NONE
93+ }
94+
95+ /// Is this key not `NodeKey::NONE`?
96+ #[inline]
97+ pub fn is_some(&self) -> bool {
98+ !self.is_none()
99+ }
100+
101+ /// Create a new `NodeKey`.
102+ ///
103+ /// `key` must not be `u32::MAX`.
104+ #[inline]
105+ pub fn new(key: u32) -> Self {
106+ debug_assert_ne!(key, u32::MAX);
107+ NodeKey(key)
108+ }
109+}
110+
111+/// An event listener callback function.
112+///
113+/// It takes three parameters:
114+///
115+/// 1. The virtual DOM's root rendering component.
116+/// 2. A capability to scheduler virtual DOM re-rendering.
117+/// 3. The event that occurred.
118+pub(crate) type ListenerCallback<'a> =
119+ &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static);
120+
121+/// An event listener.
122+pub struct Listener<'a> {
123+ /// The type of event to listen for.
124+ pub(crate) event: &'a str,
125+ /// The callback to invoke when the event happens.
126+ pub(crate) callback: ListenerCallback<'a>,
127+}
128+
129+/// An attribute on a DOM node, such as `id="my-thing"` or
130+/// `href="https://example.com"`.
23131#[derive(Clone, Debug)]
24132pub struct Attribute<'a> {
25− pub name: &'a str,
26− pub value: &'a str,
27−}
28−
29−impl<'a, Attributes, Children> BumpAllocSafe for Node<'a, Attributes, Children>
30−where
31− Attributes: BumpAllocSafe,
32− Children: BumpAllocSafe,
33−{
34−}
35−
36−impl<'a, Attributes, Children> From<&'a Node<'a, Attributes, Children>> for NodeRef<'a>
37−where
38− Attributes: AsRef<[Attribute<'a>]>,
39− Children: AsRef<[NodeRef<'a>]>,
40−{
41− fn from(node: &'a Node<'a, Attributes, Children>) -> NodeRef<'a> {
42− NodeRef {
43− data: node.data,
44− attributes: node.attributes.as_ref(),
45− children: node.children.as_ref(),
133+ pub(crate) name: &'a str,
134+ pub(crate) value: &'a str,
135+}
136+
137+impl<'a> From<CachedNode> for Node<'a> {
138+ #[inline]
139+ fn from(c: CachedNode) -> Self {
140+ Node {
141+ kind: NodeKind::Cached(c),
46142 }
47143 }
48144}
49145
50−impl<'a, Attributes, Children> From<&'a mut Node<'a, Attributes, Children>> for NodeRef<'a>
51−where
52− Attributes: AsRef<[Attribute<'a>]>,
53− Children: AsRef<[NodeRef<'a>]>,
54−{
55− fn from(node: &'a mut Node<'a, Attributes, Children>) -> NodeRef<'a> {
56− NodeRef {
57− data: node.data,
58− attributes: node.attributes.as_ref(),
59− children: node.children.as_ref(),
146+impl fmt::Debug for Listener<'_> {
147+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148+ let (a, b) = self.get_callback_parts();
149+ let a = a as *mut u32;
150+ let b = b as *mut u32;
151+ f.debug_struct("Listener")
152+ .field("event", &self.event)
153+ .field("callback", &(a, b))
154+ .finish()
155+ }
156+}
157+
158+impl<'a> Attribute<'a> {
159+ /// Get this attribute's name, such as `"id"` in `<div id="my-thing" />`.
160+ #[inline]
161+ pub fn name(&self) -> &'a str {
162+ self.name
163+ }
164+
165+ /// The attribute value, such as `"my-thing"` in `<div id="my-thing" />`.
166+ #[inline]
167+ pub fn value(&self) -> &'a str {
168+ self.value
169+ }
170+
171+ /// Certain attributes are considered "volatile" and can change via user
172+ /// input that we can't see when diffing against the old virtual DOM. For
173+ /// these attributes, we want to always re-set the attribute on the physical
174+ /// DOM node, even if the old and new virtual DOM nodes have the same value.
175+ #[inline]
176+ pub(crate) fn is_volatile(&self) -> bool {
177+ match self.name {
178+ "value" | "checked" | "selected" => true,
179+ _ => false,
60180 }
61181 }
62182}
63183
64−impl<'a, Attributes, Children> Node<'a, Attributes, Children>
65−where
66− Attributes: AsRef<[Attribute<'a>]>,
67− Children: AsRef<[NodeRef<'a>]>,
68−{
184+impl<'a> Node<'a> {
185+ /// Low-level constructor for making a new `Node` of type element with given
186+ /// parts.
187+ ///
188+ /// This is primarily intended for JSX and templating proc-macros to compile
189+ /// down into. If you are building nodes by-hand, prefer using the
190+ /// `dodrio::builder::*` APIs.
191+ #[inline]
69192 pub fn element(
193+ bump: &'a Bump,
194+ key: NodeKey,
70195 tag_name: &'a str,
71− attributes: Attributes,
72− children: Children,
73− ) -> Node<'a, Attributes, Children> {
74− let data = NodeData::Element { tag_name };
75− Node {
76− data,
196+ listeners: &'a [Listener<'a>],
197+ attributes: &'a [Attribute<'a>],
198+ children: &'a [Node<'a>],
199+ namespace: Option<&'a str>,
200+ ) -> Node<'a> {
201+ let element = bump.alloc_with(|| ElementNode {
202+ key,
203+ tag_name,
204+ listeners,
77205 attributes,
78206 children,
207+ namespace,
208+ });
209+
210+ Node {
211+ kind: NodeKind::Element(element),
79212 }
80213 }
81−}
82214
83−impl<'a> Node<'a, [Attribute<'a>; 0], [NodeRef<'a>; 0]> {
84− pub fn text(text: &'a str) -> Node<'a, [Attribute<'a>; 0], [NodeRef<'a>; 0]> {
85− let data = NodeData::Text { text };
86− let attributes = [];
87− let children = [];
215+ /// Construct a new text node with the given text.
216+ #[inline]
217+ pub(crate) fn text(text: &'a str) -> Node<'a> {
88218 Node {
89− data,
90− attributes,
91− children,
219+ kind: NodeKind::Text(TextNode { text }),
220+ }
221+ }
222+
223+ #[inline]
224+ pub(crate) fn key(&self) -> NodeKey {
225+ match &self.kind {
226+ NodeKind::Text(_) => NodeKey::NONE,
227+ NodeKind::Element(e) => e.key,
228+ NodeKind::Cached(c) => c.key,
229+ }
230+ }
231+}
232+
233+/// A node can become an iterator that yields the node itself once.
234+///
235+/// This implementation of `IntoIterator` mostly exists to improve the
236+/// `typed-html` ergonomics, where the macro invokes `.into_iter()` on the child
237+/// contents of a tag. By implementing `IntoIterator` here, we avoid having to
238+/// do nasty shenanigans like `<p>vec![$contents]</p>` instead of plain old
239+/// `<p>$contents</p>`.
240+impl<'a> IntoIterator for Node<'a> {
241+ type Item = Node<'a>;
242+ type IntoIter = iter::Once<Self::Item>;
243+
244+ fn into_iter(self) -> Self::IntoIter {
245+ iter::once(self)
246+ }
247+}
248+
249+union CallbackFatPtr<'a> {
250+ callback: ListenerCallback<'a>,
251+ parts: (u32, u32),
252+}
253+
254+impl Listener<'_> {
255+ #[inline]
256+ pub(crate) fn get_callback_parts(&self) -> (u32, u32) {
257+ assert_eq!(
258+ mem::size_of::<ListenerCallback>(),
259+ mem::size_of::<CallbackFatPtr>()
260+ );
261+
262+ unsafe {
263+ let fat = CallbackFatPtr {
264+ callback: self.callback,
265+ };
266+ let (a, b) = fat.parts;
267+ debug_assert!(a != 0);
268+ (a, b)
92269 }
93270 }
94271}
src/render.rsnew file+168
@@ -0,0 +1,168 @@
1+use crate::{Node, RenderContext};
2+use std::any::Any;
3+use std::rc::Rc;
4+use wasm_bindgen::UnwrapThrowExt;
5+
6+/// A trait for any component that can be rendered to HTML.
7+///
8+/// Takes a shared reference to `self` and generates the virtual DOM that
9+/// represents its rendered HTML.
10+///
11+/// ## `Bump` Allocation
12+///
13+/// `Render` implementations can use the `Bump` inside the provided
14+/// `RenderContext` for very fast allocation for anything that needs to be
15+/// temporarily allocated during rendering.
16+///
17+/// ## Example
18+///
19+/// ```no_run
20+/// use dodrio::{Node, Render, RenderContext};
21+///
22+/// pub struct MyComponent;
23+///
24+/// impl<'a> Render<'a> for MyComponent {
25+/// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
26+/// use dodrio::builder::*;
27+///
28+/// p(&cx)
29+/// .children([
30+/// text("This is "),
31+/// strong(&cx).children([text("my component")]).finish(),
32+/// text(" rendered!"),
33+/// ])
34+/// .finish()
35+/// }
36+/// }
37+/// ```
38+pub trait Render<'a> {
39+ /// Render `self` as a virtual DOM. Use the given context's `Bump` for
40+ /// temporary allocations.
41+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a>;
42+}
43+
44+impl<'a, 'r, R> Render<'a> for &'r R
45+where
46+ R: Render<'a>,
47+{
48+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
49+ (**self).render(cx)
50+ }
51+}
52+
53+impl<'a, R> Render<'a> for Rc<R>
54+where
55+ R: Render<'a>,
56+{
57+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
58+ (**self).render(cx)
59+ }
60+}
61+
62+/// A `RootRender` is a render component that can be the root rendering component
63+/// mounted to a virtual DOM.
64+///
65+/// In addition to rendering, it must also be `'static` so that it can be owned
66+/// by the virtual DOM and `Any` so that it can be downcast to its concrete type
67+/// by event listener callbacks.
68+///
69+/// You do not need to implement this trait by hand: there is a blanket
70+/// implementation for all `Render` types that fulfill the `RootRender`
71+/// requirements.
72+pub trait RootRender: Any + for<'a> Render<'a> {
73+ /// Get this `&RootRender` trait object as an `&Any` trait object reference.
74+ fn as_any(&self) -> &dyn Any;
75+
76+ /// Get this `&mut RootRender` trait object as an `&mut Any` trait object
77+ /// reference.
78+ fn as_any_mut(&mut self) -> &mut dyn Any;
79+}
80+
81+impl<T> RootRender for T
82+where
83+ T: Any + for<'a> Render<'a>,
84+{
85+ fn as_any(&self) -> &dyn Any {
86+ self
87+ }
88+
89+ fn as_any_mut(&mut self) -> &mut dyn Any {
90+ self
91+ }
92+}
93+
94+impl dyn RootRender {
95+ /// Downcast this shared `&dyn RootRender` trait object reference to its
96+ /// underlying concrete type.
97+ ///
98+ /// # Panics
99+ ///
100+ /// Panics if this virtual DOM's root rendering component is not an `R`
101+ /// instance.
102+ pub fn unwrap_ref<R>(&self) -> &R
103+ where
104+ R: RootRender,
105+ {
106+ self.as_any()
107+ .downcast_ref::<R>()
108+ .expect_throw("bad `RootRender::unwrap_ref` call")
109+ }
110+
111+ /// Downcast this exclusive `&mut dyn RootRender` trait object reference to
112+ /// its underlying concrete type.
113+ ///
114+ /// # Panics
115+ ///
116+ /// Panics if this virtual DOM's root rendering component is not an `R`
117+ /// instance.
118+ pub fn unwrap_mut<R>(&mut self) -> &mut R
119+ where
120+ R: RootRender,
121+ {
122+ self.as_any_mut()
123+ .downcast_mut::<R>()
124+ .expect_throw("bad `RootRender::unwrap_ref` call")
125+ }
126+}
127+
128+#[cfg(test)]
129+mod tests {
130+ #[test]
131+ fn render_is_object_safe() {
132+ #[allow(dead_code)]
133+ fn takes_dyn_render(_: &dyn super::Render) {}
134+ }
135+
136+ #[test]
137+ fn root_render_is_object_safe() {
138+ #[allow(dead_code)]
139+ fn takes_dyn_render(_: &dyn super::RootRender) {}
140+ }
141+
142+ #[test]
143+ fn render_bump_scoped_child() {
144+ use crate::{builder::*, bumpalo::collections::String, Node, Render, RenderContext};
145+
146+ struct Child<'a> {
147+ name: &'a str,
148+ }
149+
150+ impl<'a> Render<'a> for Child<'a> {
151+ fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
152+ text(self.name)
153+ }
154+ }
155+
156+ struct Parent;
157+
158+ impl<'a> Render<'a> for Parent {
159+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
160+ let child_name = String::from_str_in("child", cx.bump).into_bump_str();
161+
162+ div(&cx)
163+ .children([Child { name: child_name }.render(cx)])
164+ .finish()
165+ }
166+ }
167+ }
168+}
src/render_context.rsnew file+104
@@ -0,0 +1,104 @@
1+use crate::{
2+ cached::{Cached, TemplateId},
3+ cached_set::{CacheId, CachedSet},
4+ Node, Render,
5+};
6+use bumpalo::Bump;
7+use fxhash::FxHashMap;
8+use std::fmt;
9+
10+/// Common context available to all `Render` implementations.
11+///
12+/// Notably, the `RenderContext` gives access to the bump arena that the virtual
13+/// DOM should be allocated within. This is available via the `bump` field.
14+pub struct RenderContext<'a> {
15+ /// The underlying bump arena that virtual DOMs are rendered into.
16+ ///
17+ /// ## Example
18+ ///
19+ /// ```
20+ /// use dodrio::RenderContext;
21+ ///
22+ /// // Given a rendering context, allocate an i32 inside its bump arena.
23+ /// fn foo<'a>(cx: &mut RenderContext<'a>) -> &'a mut i32 {
24+ /// cx.bump.alloc(42)
25+ /// }
26+ /// ```
27+ pub bump: &'a Bump,
28+
29+ pub(crate) cached_set: &'a crate::RefCell<CachedSet>,
30+
31+ pub(crate) templates: &'a mut FxHashMap<TemplateId, Option<CacheId>>,
32+
33+ // Prevent exhaustive matching on the rendering context, so we can always
34+ // add more members in a semver-compatible way.
35+ _non_exhaustive: (),
36+}
37+
38+impl fmt::Debug for RenderContext<'_> {
39+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40+ f.debug_struct("RenderContext")
41+ .field("bump", &self.bump)
42+ .finish()
43+ }
44+}
45+
46+impl<'a> RenderContext<'a> {
47+ pub_unstable_internal! {
48+ pub(crate) fn new(
49+ bump: &'a Bump,
50+ cached_set: &'a crate::RefCell<CachedSet>,
51+ templates: &'a mut FxHashMap<TemplateId, Option<CacheId>>
52+ ) -> Self {
53+ RenderContext {
54+ bump,
55+ cached_set,
56+ templates,
57+ _non_exhaustive: (),
58+ }
59+ }
60+ }
61+
62+ pub(crate) fn cache<F>(&mut self, pinned: bool, template: Option<CacheId>, f: F) -> CacheId
63+ where
64+ F: for<'b> FnOnce(&mut RenderContext<'b>) -> Node<'b>,
65+ {
66+ CachedSet::insert(self, pinned, template, f)
67+ }
68+
69+ /// Get or create the cached template for `Cached<R>`.
70+ pub(crate) fn template<R>(&mut self) -> Option<CacheId>
71+ where
72+ R: 'static + Default + for<'b> Render<'b>,
73+ {
74+ let template_id = Cached::<R>::template_id();
75+ if let Some(cache_id) = self.templates.get(&template_id).cloned() {
76+ return cache_id;
77+ }
78+
79+ // Prevent re-entrancy from infinite looping. Any attempts to get `R`'s
80+ // template while constructing the template will simply fail to use the
81+ // templated fast path.
82+ self.templates.insert(template_id, None);
83+
84+ // Render the default `R` and save that as the template for all
85+ // `Cached<R>`s.
86+ let cache_id = self.cache(true, None, |nested_cx| R::default().render(nested_cx));
87+ self.templates.insert(template_id, Some(cache_id));
88+ Some(cache_id)
89+ }
90+}
91+
92+impl<'a, 'b> From<&'b RenderContext<'a>> for &'a Bump {
93+ #[inline]
94+ fn from(cx: &'b RenderContext<'a>) -> &'a Bump {
95+ cx.bump
96+ }
97+}
98+
99+impl<'a, 'b, 'c> From<&'c &'b mut RenderContext<'a>> for &'a Bump {
100+ #[inline]
101+ fn from(cx: &'c &'b mut RenderContext<'a>) -> &'a Bump {
102+ cx.bump
103+ }
104+}
src/strace.rsnew file+22
@@ -0,0 +1,22 @@
1+cfg_if::cfg_if! {
2+ if #[cfg(feature = "xxx-unstable-strace")] {
3+ use wasm_bindgen::prelude::*;
4+
5+ #[wasm_bindgen(module = "/js/strace.js")]
6+ extern "C" {
7+ #[wasm_bindgen(js_name = initStrace)]
8+ fn really_init_strace();
9+ }
10+
11+ pub fn init_strace() {
12+ use std::sync::Once;
13+ static STRACE: Once = Once::new();
14+ STRACE.call_once(|| {
15+ really_init_strace();
16+ });
17+ }
18+ } else {
19+ #[inline]
20+ pub fn init_strace() {}
21+ }
22+}
src/vdom.rs+429−142
@@ -1,189 +1,476 @@
1−use super::change_list::ChangeList;
2−use super::node::{Node, NodeData, NodeRef};
3−use super::Render;
1+use super::change_list::ChangeListPersistentState;
2+use super::RootRender;
3+use crate::cached::TemplateId;
4+use crate::cached_set::{CacheId, CachedSet};
5+use crate::events::EventsRegistry;
6+use crate::node::{Node, NodeKey};
7+use crate::RenderContext;
48use bumpalo::Bump;
9+use fxhash::FxHashMap;
10+use std::cell::Cell;
11+use std::cell::RefCell;
12+use std::fmt;
13+use std::future::Future;
514use std::mem;
15+use std::mem::ManuallyDrop;
16+use std::rc::{Rc, Weak};
17+use wasm_bindgen::prelude::*;
18+use wasm_bindgen::JsCast;
19+use wasm_bindgen_futures::JsFuture;
620
21+/// A strong handle to a mounted virtual DOM.
22+///
23+/// When this handle is dropped, the virtual DOM is unmounted and its listeners
24+/// removed. To keep it mounted forever, use the `Vdom::forget` method.
25+#[must_use = "A `Vdom` will only keep rendering and listening to events while it has not been \
26+ dropped. If you want a `Vdom` to run forever, call `Vdom::forget`."]
27+#[derive(Debug)]
728pub struct Vdom {
8− dom_buffers: [Bump; 2],
9− change_list: ChangeList,
29+ inner: Rc<VdomInner>,
30+}
31+
32+/// A weak handle to a virtual DOM.
33+///
34+/// Does not prevent the virtual DOM from being unmounted: only keeping the
35+/// original `Vdom` alive guarantees that.
36+///
37+/// A `VdomWeak` also gives you the capability to scheduling re-rendering (say
38+/// after mutating the render component state).
39+#[derive(Clone, Debug)]
40+pub struct VdomWeak {
41+ inner: Weak<VdomInner>,
42+}
43+
44+#[derive(Debug)]
45+pub(crate) struct VdomInner {
46+ pub(crate) shared: VdomInnerShared,
47+ pub(crate) exclusive: RefCell<VdomInnerExclusive>,
48+}
49+
50+pub(crate) struct VdomInnerShared {
51+ pub(crate) render_scheduled: Cell<Option<js_sys::Promise>>,
52+}
1053
11− // Actually a reference into `self.dom_buffers[0]`.
12− current_root: Option<NodeRef<'static>>,
54+pub(crate) struct VdomInnerExclusive {
55+ // Always `Some` except just before we drop. Just an option so that
56+ // `unmount` can take the component out but we can still have a Drop
57+ // implementation.
58+ component: Option<Box<dyn RootRender>>,
59+
60+ dom_buffers: Option<[Bump; 2]>,
61+ change_list: ManuallyDrop<ChangeListPersistentState>,
62+ container: crate::Element,
63+ events_registry: Option<Rc<RefCell<EventsRegistry>>>,
64+ events_trampoline: Option<crate::EventsTrampoline>,
65+ cached_set: crate::RefCell<CachedSet>,
66+ templates: FxHashMap<TemplateId, Option<CacheId>>,
67+
68+ // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
69+ // caching renders, into `self.component`'s bump.
70+ current_root: Option<Node<'static>>,
1371}
1472
15−unsafe fn extend_node_lifetime<'a>(node: NodeRef<'a>) -> NodeRef<'static> {
73+unsafe fn extend_node_lifetime<'a>(node: Node<'a>) -> Node<'static> {
1674 mem::transmute(node)
1775}
1876
77+impl fmt::Debug for VdomInnerShared {
78+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79+ let render_scheduled = Cell::new(None);
80+ self.render_scheduled.swap(&render_scheduled);
81+ let render_scheduled = render_scheduled.into_inner();
82+ let r = f
83+ .debug_struct("VdomInnerShared")
84+ .field("render_scheduled", &render_scheduled)
85+ .finish();
86+ self.render_scheduled.set(render_scheduled);
87+ r
88+ }
89+}
90+
91+impl fmt::Debug for VdomInnerExclusive {
92+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93+ f.debug_struct("VdomInnerExclusive")
94+ .field("component", &"..")
95+ .field("dom_buffers", &self.dom_buffers)
96+ .field("change_list", &self.change_list)
97+ .field("container", &self.container)
98+ .field("events_registry", &self.events_registry)
99+ .field("events_trampoline", &"..")
100+ .field("current_root", &self.current_root)
101+ .finish()
102+ }
103+}
104+
105+impl Drop for VdomInnerExclusive {
106+ fn drop(&mut self) {
107+ debug!("Dropping VdomInnerExclusive");
108+
109+ // Make sure that we clean up our JS listeners and all that before we
110+ // empty the container.
111+ unsafe {
112+ ManuallyDrop::drop(&mut self.change_list);
113+ }
114+
115+ let registry = self.events_registry.take().unwrap_throw();
116+ let mut registry = registry.borrow_mut();
117+ registry.clear_active_listeners();
118+
119+ empty_container(&self.container);
120+ }
121+}
122+
123+cfg_if::cfg_if! {
124+ if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] {
125+ fn empty_container(_container: &crate::Element) {}
126+ fn initialize_container(_container: &crate::Element) {}
127+ } else {
128+ fn empty_container(container: &crate::Element) {
129+ container.set_inner_html("");
130+ }
131+
132+ fn initialize_container(container: &crate::Element) {
133+ empty_container(container);
134+
135+ // Create the dummy `<div/>` child in the container.
136+ let window = web_sys::window().expect_throw("should have access to the Window");
137+ let document = window
138+ .document()
139+ .expect("should have access to the Document");
140+ container
141+ .append_child(
142+ document
143+ .create_element("div")
144+ .expect("should create element OK")
145+ .as_ref(),
146+ )
147+ .expect("should append child OK");
148+ }
149+ }
150+}
151+
19152impl Vdom {
20− pub fn new<R>(container: &web_sys::Node, contents: R) -> Vdom
153+ /// Mount a new `Vdom` in the given container element with the given root
154+ /// rendering component.
155+ ///
156+ /// This will box the given component into trait object.
157+ pub fn new<R>(container: &crate::Element, component: R) -> Vdom
21158 where
22− R: Render,
159+ R: RootRender,
23160 {
161+ Self::with_boxed_root_render(container, Box::new(component) as Box<dyn RootRender>)
162+ }
163+
164+ /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
165+ /// rendering component.
166+ pub fn with_boxed_root_render(
167+ container: &crate::Element,
168+ component: Box<dyn RootRender>,
169+ ) -> Vdom {
170+ crate::strace::init_strace();
171+
24172 let dom_buffers = [Bump::new(), Bump::new()];
25− let change_list = ChangeList::new(container);
173+ let change_list = ManuallyDrop::new(ChangeListPersistentState::new(container));
26174
27175 // Create a dummy `<div/>` in our container.
28− let current_root: NodeRef = dom_buffers[0].alloc(Node::element("div", [], [])).into();
176+ initialize_container(container);
177+ let current_root =
178+ Node::element(&dom_buffers[0], NodeKey::NONE, "div", &[], &[], &[], None);
29179 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
30− let window = web_sys::window().expect("should have acess to the Window");
31− let document = window
32− .document()
33− .expect("should have access to the Document");
34− container
35− .append_child(
36− document
37− .create_element("div")
38− .expect("should create element OK")
39− .as_ref(),
40− )
41− .expect("should append child OK");
42−
43− // Diff and apply the `contents` against this dummy node.
44− let mut vdom = Vdom {
45− dom_buffers,
46− change_list,
47− current_root,
48− };
49− vdom.render(contents);
50− vdom
180+
181+ let container = container.clone();
182+ let inner = Rc::new(VdomInner {
183+ shared: VdomInnerShared {
184+ render_scheduled: Cell::new(None),
185+ },
186+ exclusive: RefCell::new(VdomInnerExclusive {
187+ component: Some(component),
188+ dom_buffers: Some(dom_buffers),
189+ change_list,
190+ container,
191+ current_root,
192+ events_registry: None,
193+ events_trampoline: None,
194+ cached_set: crate::RefCell::new(Default::default()),
195+ templates: Default::default(),
196+ }),
197+ });
198+
199+ let (events_registry, events_trampoline) = EventsRegistry::new(Rc::downgrade(&inner));
200+
201+ {
202+ let mut inner = inner.exclusive.borrow_mut();
203+ inner.events_registry = Some(events_registry);
204+ inner.change_list.init_events_trampoline(&events_trampoline);
205+ debug_assert!(inner.events_trampoline.is_none());
206+ inner.events_trampoline = Some(events_trampoline);
207+
208+ // Diff and apply the `contents` against our dummy `<div/>`.
209+ inner.render();
210+ }
211+
212+ Vdom { inner }
51213 }
52214
53− pub fn render<R>(&mut self, contents: R)
215+ /// Immediately re-render and diff. Only for internal testing and
216+ /// benchmarking purposes.
217+ #[cfg(feature = "xxx-unstable-internal-use-only")]
218+ pub fn immediately_render_and_diff<R>(&self, component: R)
54219 where
55− R: Render,
220+ R: RootRender,
56221 {
222+ let mut exclusive = self.inner.exclusive.borrow_mut();
223+ let component = Box::new(component) as Box<dyn RootRender>;
224+ exclusive.component = Some(component);
225+ exclusive.render();
226+ }
227+
228+ /// Run this virtual DOM and its listeners forever and never unmount it.
229+ #[inline]
230+ pub fn forget(self) {
231+ mem::forget(self);
232+ }
233+
234+ /// Get a weak handle to this virtual DOM.
235+ #[inline]
236+ pub fn weak(&self) -> VdomWeak {
237+ VdomWeak::new(&self.inner)
238+ }
239+
240+ /// Unmount this virtual DOM, unregister its event listeners, and return its
241+ /// root render component.
242+ #[inline]
243+ pub fn unmount(self) -> Box<dyn RootRender> {
244+ Rc::try_unwrap(self.inner.clone())
245+ .map_err(|_| ())
246+ .unwrap_throw()
247+ .exclusive
248+ .into_inner()
249+ .component
250+ .take()
251+ .unwrap_throw()
252+ }
253+}
254+
255+impl VdomInnerExclusive {
256+ /// Get an exclusive reference to the underlying render component as a raw
257+ /// trait object.
258+ #[inline]
259+ pub(crate) fn component_raw_mut(&mut self) -> &mut dyn RootRender {
260+ &mut **self.component.as_mut().unwrap_throw()
261+ }
262+
263+ /// Re-render this virtual dom's current component.
264+ pub(crate) fn render(&mut self) {
57265 unsafe {
58− self.dom_buffers[1].reset();
59− let new_contents = contents.render(&self.dom_buffers[1]);
60− let new_contents = extend_node_lifetime(new_contents);
266+ let events_registry = self.events_registry.take().unwrap();
267+ {
268+ let mut registry = events_registry.borrow_mut();
61269
62− let old_contents = self.current_root.take().unwrap();
63− self.diff(old_contents, new_contents.clone());
270+ // Reset the inactive bump arena's pointer.
271+ let mut dom_buffers = self.dom_buffers.take().unwrap_throw();
272+ dom_buffers[1].reset();
64273
65− self.swap_buffers();
66− self.set_current_root(new_contents);
274+ // Render the new current contents into the inactive bump arena.
275+ let mut cx =
276+ RenderContext::new(&dom_buffers[1], &self.cached_set, &mut self.templates);
277+ let new_contents = self.component.as_ref().unwrap_throw().render(&mut cx);
278+ let new_contents = extend_node_lifetime(new_contents);
279+
280+ // Diff the old contents with the new contents.
281+ let old_contents = self.current_root.take().unwrap();
282+ let mut cache_roots;
283+ {
284+ let cached_set = self.cached_set.borrow();
285+ cache_roots = cached_set.new_roots_set();
286+ let mut change_list = self.change_list.builder();
287+ crate::diff::diff(
288+ &cached_set,
289+ &mut change_list,
290+ &mut registry,
291+ &old_contents,
292+ &new_contents,
293+ &mut cache_roots,
294+ );
67295
68− self.change_list.apply_changes();
296+ // Tell JS to apply our diff-generated changes to the physical DOM!
297+ change_list.finish();
298+ }
299+
300+ {
301+ // Clean up unused cached renders.
302+ let mut cached_set = self.cached_set.borrow_mut();
303+ cached_set.gc(&mut registry, cache_roots);
304+ }
305+
306+ // Swap the buffers to make the bump arena with the new contents the
307+ // active arena, and the old one into the inactive arena.
308+ self.swap_buffers(dom_buffers);
309+ self.set_current_root(new_contents);
310+ }
311+
312+ self.events_registry = Some(events_registry);
69313 }
70314 }
71315
72− fn swap_buffers(&mut self) {
73− let (first, second) = self.dom_buffers.as_mut().split_at_mut(1);
316+ fn swap_buffers(&mut self, mut dom_buffers: [Bump; 2]) {
317+ debug_assert!(self.dom_buffers.is_none());
318+ let (first, second) = dom_buffers.as_mut().split_at_mut(1);
74319 mem::swap(&mut first[0], &mut second[0]);
320+ self.dom_buffers = Some(dom_buffers);
75321 }
76322
77− unsafe fn set_current_root(&mut self, current: NodeRef<'static>) {
323+ unsafe fn set_current_root(&mut self, current: Node<'static>) {
78324 debug_assert!(self.current_root.is_none());
79325 self.current_root = Some(current);
80326 }
327+}
81328
82− fn diff(&self, old: NodeRef, new: NodeRef) {
83− match (new.data, old.data) {
84− (NodeData::Text { text: new_text }, NodeData::Text { text: old_text }) => {
85− if new_text != old_text {
86− self.change_list.emit_set_text(new_text);
87− }
88− }
89− (NodeData::Text { .. }, NodeData::Element { .. }) => {
90− self.create(new);
91− self.change_list.emit_replace_with();
92− }
93− (NodeData::Element { .. }, NodeData::Text { .. }) => {
94− self.create(new);
95− self.change_list.emit_replace_with();
96− }
97− (
98− NodeData::Element {
99− tag_name: new_tag_name,
100− },
101− NodeData::Element {
102− tag_name: old_tag_name,
103− },
104− ) => {
105− if new_tag_name != old_tag_name {
106− self.create(new);
107− self.change_list.emit_replace_with();
108− return;
109− }
329+fn request_animation_frame(f: &Closure<dyn FnMut()>) {
330+ web_sys::window()
331+ .expect_throw("should have a window")
332+ .request_animation_frame(f.as_ref().unchecked_ref())
333+ .expect_throw("should register `requestAnimationFrame` OK");
334+}
110335
111− // Do O(n^2) passes to add/update and remove attributes, since
112− // there are almost always very few attributes.
113− 'outer: for new_attr in new.attributes {
114− for old_attr in old.attributes {
115− if old_attr.name == new_attr.name {
116− if old_attr.value != new_attr.value {
117− self.change_list
118− .emit_set_attribute(new_attr.name, new_attr.value);
119− }
120− continue 'outer;
121− }
122− }
123− self.change_list
124− .emit_set_attribute(new_attr.name, new_attr.value);
125− }
126− 'outer2: for old_attr in old.attributes {
127− for new_attr in new.attributes {
128− if old_attr.name == new_attr.name {
129− continue 'outer2;
130− }
131− }
132− self.change_list.emit_remove_attribute(old_attr.name);
133− }
336+fn with_animation_frame<F>(mut f: F)
337+where
338+ F: 'static + FnMut(),
339+{
340+ let g = Rc::new(RefCell::new(None));
341+ let h = g.clone();
134342
135− let mut new_children = new.children.iter();
136− let mut old_children = old.children.iter();
137− let mut pushed_first_child = false;
343+ let f = Closure::wrap(Box::new(move || {
344+ *g.borrow_mut() = None;
345+ f();
346+ }) as Box<dyn FnMut()>);
347+ request_animation_frame(&f);
138348
139− for (i, (new_child, old_child)) in
140− new_children.by_ref().zip(old_children.by_ref()).enumerate()
141− {
142− if i == 0 {
143− self.change_list.emit_push_first_child();
144− pushed_first_child = true;
145− } else {
146− self.change_list.emit_pop_push_next_sibling();
147− }
148−
149− self.diff(old_child.clone(), new_child.clone());
150− }
349+ *h.borrow_mut() = Some(f);
350+}
151351
152− if old_children.next().is_some() {
153− if !pushed_first_child {
154− self.change_list.emit_push_first_child();
155− }
156− self.change_list.emit_remove_self_and_next_siblings();
157− }
352+/// An operation failed because the virtual DOM was already dropped and
353+/// unmounted.
354+#[derive(Debug)]
355+pub struct VdomDroppedError {}
158356
159− for (i, new_child) in new_children.enumerate() {
160− if i == 0 && pushed_first_child {
161− self.change_list.emit_pop();
162− }
163− self.create(new_child.clone());
164− self.change_list.emit_append_child();
165− }
357+impl fmt::Display for VdomDroppedError {
358+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
359+ write!(f, "The virtual DOM was dropped.")
360+ }
361+}
166362
167− self.change_list.emit_pop();
168− }
363+impl std::error::Error for VdomDroppedError {}
364+
365+impl VdomWeak {
366+ /// Construct a new weak handle to the given virtual DOM.
367+ #[inline]
368+ pub(crate) fn new(inner: &Rc<VdomInner>) -> VdomWeak {
369+ VdomWeak {
370+ inner: Rc::downgrade(inner),
169371 }
170372 }
171373
172− fn create(&self, node: NodeRef) {
173− match node.data {
174− NodeData::Text { text } => {
175− self.change_list.emit_create_text_node(text);
176− }
177− NodeData::Element { tag_name } => {
178− self.change_list.emit_create_element(tag_name);
179− for attr in node.attributes {
180− self.change_list.emit_set_attribute(&attr.name, &attr.value);
181− }
182− for child in node.children {
183− self.create(child.clone());
184− self.change_list.emit_append_child();
185− }
186− }
374+ /// Replace the root rendering component with the new `root`.
375+ ///
376+ /// Returns a future that resolves to the *old* root component.
377+ pub async fn set_component(
378+ self,
379+ root: Box<dyn RootRender>,
380+ ) -> Result<Box<dyn RootRender + 'static>, VdomDroppedError> {
381+ let inner = self.inner.upgrade().ok_or(VdomDroppedError {})?;
382+
383+ // Wait for a new tick of the micro-task queue
384+ let _ = JsFuture::from(js_sys::Promise::resolve(&JsValue::null())).await;
385+
386+ let old = {
387+ let mut exclusive = inner.exclusive.borrow_mut();
388+ mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
389+ };
390+
391+ VdomWeak::new(&inner).render().await?;
392+
393+ Ok(old)
394+ }
395+
396+ /// Execute `f` with a reference to this virtual DOM's root rendering
397+ /// component.
398+ ///
399+ /// To ensure exclusive access to the root rendering component, the
400+ /// invocation takes place on a new tick of the micro-task queue.
401+ pub async fn with_component<F, T>(&self, f: F) -> Result<T, VdomDroppedError>
402+ where
403+ F: 'static + FnOnce(&mut dyn RootRender) -> T,
404+ {
405+ let inner = self.inner.upgrade().ok_or(VdomDroppedError {})?;
406+
407+ // Wait for a new tick of the micro-task queue
408+ let _ = JsFuture::from(js_sys::Promise::resolve(&JsValue::null())).await;
409+
410+ let mut exclusive = inner.exclusive.borrow_mut();
411+
412+ Ok(f(exclusive.component_raw_mut()))
413+ }
414+
415+ /// Schedule a render to occur during the next animation frame.
416+ ///
417+ /// If you want a future that resolves after the render has finished, use
418+ /// `render` instead.
419+ pub fn schedule_render(&self) {
420+ debug!("VdomWeak::schedule_render");
421+
422+ let future = self.render();
423+
424+ wasm_bindgen_futures::spawn_local(async move {
425+ let _ = future.await;
426+ });
427+ }
428+
429+ /// Schedule a render to occur during the next animation frame and return a
430+ /// future that will complete once the render has finished.
431+ ///
432+ /// If you don't want to do more things after the render completes, then use
433+ /// `schedule_render` instead of `render`.
434+ pub fn render(&self) -> impl Future<Output = Result<(), VdomDroppedError>> {
435+ let inner = self.inner.upgrade();
436+
437+ async move {
438+ let inner = inner.ok_or(VdomDroppedError {})?;
439+
440+ let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
441+ js_sys::Promise::new(&mut |resolve, reject| {
442+ let vdom = VdomWeak {
443+ inner: Rc::downgrade(&inner),
444+ };
445+
446+ with_animation_frame(move || match vdom.inner.upgrade() {
447+ None => {
448+ warn!("VdomWeak::render: vdom unmounted before we could render");
449+ let r = reject.call0(&JsValue::null());
450+ debug_assert!(r.is_ok());
451+ }
452+ Some(inner) => {
453+ let mut exclusive = inner.exclusive.borrow_mut();
454+ exclusive.render();
455+
456+ // We did the render, so take the promise away
457+ // and let future `render` calls request new
458+ // animation frames.
459+ let _ = inner.shared.render_scheduled.take();
460+
461+ let r = resolve.call0(&JsValue::null());
462+ debug_assert!(r.is_ok());
463+ }
464+ });
465+ })
466+ });
467+
468+ inner.shared.render_scheduled.set(Some(promise.clone()));
469+
470+ JsFuture::from(promise)
471+ .await
472+ .map(drop)
473+ .map_err(|_| VdomDroppedError {})
187474 }
188475 }
189476}
tests/web.rsdeleted−13
@@ -1,13 +0,0 @@
1−//! Test suite for the Web and headless browsers.
2−
3−#![cfg(target_arch = "wasm32")]
4−
5−extern crate wasm_bindgen_test;
6−use wasm_bindgen_test::*;
7−
8−wasm_bindgen_test_configure!(run_in_browser);
9−
10−#[wasm_bindgen_test]
11−fn pass() {
12− assert_eq!(1 + 1, 2);
13−}
tests/web/cached.jsnew file+11
@@ -0,0 +1,11 @@
1+let cloneNodeCount = 0;
2+
3+const origCloneNode = Node.prototype.cloneNode;
4+Node.prototype.cloneNode = function (...args) {
5+ cloneNodeCount += 1;
6+ return origCloneNode.apply(this, args);
7+};
8+
9+export function getCloneNodeCount() {
10+ return cloneNodeCount;
11+}
tests/web/cached.rsnew file+289
@@ -0,0 +1,289 @@
1+use super::{assert_rendered, before_after, create_element, RenderFn};
2+use dodrio::{builder::*, bumpalo, Cached, Node, Render, RenderContext, RootRender, Vdom};
3+use std::cell::Cell;
4+use std::rc::Rc;
5+use wasm_bindgen::prelude::*;
6+use wasm_bindgen_test::*;
7+
8+#[derive(Default)]
9+pub struct CountRenders {
10+ render_count: Cell<usize>,
11+}
12+
13+impl CountRenders {
14+ pub fn new() -> CountRenders {
15+ CountRenders {
16+ render_count: Cell::new(0),
17+ }
18+ }
19+}
20+
21+impl<'a> Render<'a> for CountRenders {
22+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
23+ let count = self.render_count.get() + 1;
24+ self.render_count.set(count);
25+
26+ let s = bumpalo::format!(in cx.bump, "{}", count);
27+ text(s.into_bump_str())
28+ }
29+}
30+
31+#[wasm_bindgen_test]
32+async fn uses_cached_render() {
33+ use dodrio::builder::*;
34+
35+ let cached = Cached::new(CountRenders::new());
36+
37+ let container0 = create_element("div");
38+ let container1 = container0.clone();
39+ let container2 = container0.clone();
40+ let container3 = container0.clone();
41+ let container4 = container0.clone();
42+
43+ let vdom0 = Rc::new(Vdom::new(&container0, cached));
44+ let vdom1 = vdom0.clone();
45+ let vdom2 = vdom0.clone();
46+ let vdom3 = vdom0.clone();
47+ let vdom4 = vdom0.clone();
48+ let vdom5 = vdom0.clone();
49+ let vdom6 = vdom0.clone();
50+ let vdom7 = vdom0.clone();
51+
52+ // We render, populate the cache, and get "1".
53+ vdom0.weak().render().await.unwrap();
54+ vdom0
55+ .weak()
56+ .with_component(move |comp| {
57+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
58+ assert_eq!(comp.render_count.get(), 1);
59+ assert_rendered(&container1, &RenderFn(|_| text("1")));
60+ })
61+ .await
62+ .unwrap();
63+
64+ // We re-render, re-use the cached node, and get "1" again.
65+ vdom1.weak().render().await.unwrap();
66+ vdom2
67+ .weak()
68+ .with_component(move |comp| {
69+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
70+ assert_eq!(comp.render_count.get(), 1);
71+ assert_rendered(&container2, &RenderFn(|_| text("1")));
72+ })
73+ .await
74+ .unwrap();
75+
76+ // We invalidate the cache, re-render, re-populate the cache, and should now have "2".
77+ vdom3
78+ .weak()
79+ .with_component(move |comp| {
80+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
81+ Cached::invalidate(comp);
82+ })
83+ .await
84+ .unwrap();
85+ vdom4.weak().render().await.unwrap();
86+ vdom5
87+ .weak()
88+ .with_component(move |comp| {
89+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
90+ assert_eq!(comp.render_count.get(), 2);
91+ assert_rendered(&container3, &RenderFn(|_| text("2")));
92+ })
93+ .await
94+ .unwrap();
95+
96+ // We re-render, re-use the cached node, and get "2" again.
97+ vdom6.weak().render().await.unwrap();
98+ vdom7
99+ .weak()
100+ .with_component(move |comp| {
101+ let comp = comp.unwrap_mut::<Cached<CountRenders>>();
102+ assert_eq!(comp.render_count.get(), 2);
103+ assert_rendered(&container4, &RenderFn(|_| text("2")));
104+ })
105+ .await
106+ .unwrap();
107+}
108+
109+#[wasm_bindgen(module = "/tests/web/cached.js")]
110+extern "C" {
111+ #[wasm_bindgen(js_name = getCloneNodeCount)]
112+ fn get_clone_node_count() -> u32;
113+}
114+
115+#[wasm_bindgen_test]
116+async fn creating_cached_nodes_uses_clone_node() {
117+ use dodrio::builder::*;
118+
119+ let container = create_element("div");
120+
121+ // The initial vdom render should populate the cache and save a template.
122+ let vdom0 = Rc::new(Vdom::new(&container, Cached::new(CountRenders::new())));
123+ let vdom1 = vdom0.clone();
124+ let _vdom2 = vdom0.clone();
125+
126+ let clone_node_count_before = get_clone_node_count();
127+
128+ // Render something entirely different...
129+ vdom0
130+ .weak()
131+ .set_component(Box::new(RenderFn(|_| text("hi"))) as Box<dyn RootRender>)
132+ .await
133+ .unwrap();
134+
135+ // Re-render our cached node. Since we are creating it from scratch,
136+ // and already have a template from the earlier render, we should
137+ // call `cloneNode` here.
138+ vdom1
139+ .weak()
140+ .set_component(Box::new(Cached::new(CountRenders::new())) as Box<dyn RootRender>)
141+ .await
142+ .unwrap();
143+
144+ let clone_node_count_after = get_clone_node_count();
145+ assert_eq!(clone_node_count_after, clone_node_count_before + 1);
146+}
147+
148+struct Id(&'static str);
149+
150+impl Default for Id {
151+ fn default() -> Id {
152+ Id("")
153+ }
154+}
155+
156+impl<'a> Render<'a> for Id {
157+ fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
158+ text(self.0)
159+ }
160+}
161+
162+thread_local! {
163+ static WARM_CHEESE: Rc<Cached<Id>> = Rc::new(Cached::new(Id("cheese")));
164+ static WARM_CHEESIER: Rc<Cached<Id>> = Rc::new(Cached::new(Id("cheesier")));
165+}
166+
167+fn warm_cheese<'a>(cx: &mut RenderContext<'a>) -> Node<'a> {
168+ WARM_CHEESE.with(|c| {
169+ let _ = c.render(cx);
170+ c.render(cx)
171+ })
172+}
173+
174+fn warm_cheesier<'a>(cx: &mut RenderContext<'a>) -> Node<'a> {
175+ WARM_CHEESIER.with(|c| {
176+ let _ = c.render(cx);
177+ c.render(cx)
178+ })
179+}
180+
181+before_after! {
182+ cold_cache_and_not_cached {
183+ before(cx) {
184+ Cached::new(Id("cheese")).render(cx)
185+ }
186+ after(cx) {
187+ Id("ravioli").render(cx)
188+ }
189+ }
190+
191+ not_cached_and_cold_cache {
192+ before(cx) {
193+ Id("ravioli").render(cx)
194+ }
195+ after(cx) {
196+ Cached::new(Id("cheese")).render(cx)
197+ }
198+ }
199+
200+ cold_cache_and_cold_cache_same {
201+ before(cx) {
202+ Cached::new(Id("cheese")).render(cx)
203+ }
204+ after(cx) {
205+ Cached::new(Id("cheese")).render(cx)
206+ }
207+ }
208+
209+ cold_cache_and_cold_cache_different {
210+ before(cx) {
211+ Cached::new(Id("cheese")).render(cx)
212+ }
213+ after(cx) {
214+ Cached::new(Id("ravioli")).render(cx)
215+ }
216+ }
217+
218+ warm_cache_and_not_cached {
219+ before(cx) {
220+ warm_cheese(cx)
221+ }
222+ after(cx) {
223+ Id("ravioli").render(cx)
224+ }
225+ }
226+
227+ not_cached_and_warm_cache {
228+ before(cx) {
229+ Id("ravioli").render(cx)
230+ }
231+ after(cx) {
232+ warm_cheese(cx)
233+ }
234+ }
235+
236+ warm_cache_and_warm_cache_same {
237+ before(cx) {
238+ warm_cheese(cx)
239+ }
240+ after(cx) {
241+ warm_cheese(cx)
242+ }
243+ }
244+
245+ warm_cache_and_warm_cache_different {
246+ before(cx) {
247+ warm_cheese(cx)
248+ }
249+ after(cx) {
250+ warm_cheesier(cx)
251+ }
252+ }
253+
254+ cold_cache_and_warm_cache_same {
255+ before(cx) {
256+ Cached::new(Id("cheese")).render(cx)
257+ }
258+ after(cx) {
259+ warm_cheese(cx)
260+ }
261+ }
262+
263+ cold_cache_and_warm_cache_different {
264+ before(cx) {
265+ Cached::new(Id("cheese")).render(cx)
266+ }
267+ after(cx) {
268+ warm_cheesier(cx)
269+ }
270+ }
271+
272+ warm_cache_and_cold_cache_same {
273+ before(cx) {
274+ warm_cheese(cx)
275+ }
276+ after(cx) {
277+ Cached::new(Id("cheese")).render(cx)
278+ }
279+ }
280+
281+ warm_cache_and_cold_cache_different {
282+ before(cx) {
283+ warm_cheesier(cx)
284+ }
285+ after(cx) {
286+ Cached::new(Id("cheese")).render(cx)
287+ }
288+ }
289+}
tests/web/events.rsnew file+250
@@ -0,0 +1,250 @@
1+use super::create_element;
2+use dodrio::{Node, Render, RenderContext, Vdom};
3+use futures::future::{select, Either};
4+use std::cell::{Cell, RefCell};
5+use std::rc::Rc;
6+use wasm_bindgen::prelude::*;
7+use wasm_bindgen::JsCast;
8+use wasm_bindgen_test::*;
9+
10+struct EventContainer {
11+ event: &'static str,
12+ on_event: Box<dyn FnMut()>,
13+}
14+
15+impl EventContainer {
16+ fn new<F>(event: &'static str, on_event: F) -> EventContainer
17+ where
18+ F: 'static + FnMut(),
19+ {
20+ EventContainer {
21+ event,
22+ on_event: Box::new(on_event),
23+ }
24+ }
25+}
26+
27+impl<'a> Render<'a> for EventContainer {
28+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
29+ use dodrio::builder::*;
30+ div(&cx)
31+ .attr("id", "target")
32+ .on(self.event, |root, _scheduler, _event| {
33+ (root.unwrap_mut::<EventContainer>().on_event)();
34+ })
35+ .finish()
36+ }
37+}
38+
39+fn target(container: &web_sys::Element) -> web_sys::HtmlElement {
40+ container
41+ .query_selector("#target")
42+ .expect_throw("should querySelector OK")
43+ .expect_throw("should find `#target` in container")
44+ .unchecked_into()
45+}
46+
47+#[wasm_bindgen_test]
48+async fn click() {
49+ let container = create_element("div");
50+
51+ let (sender, receiver) = futures::channel::oneshot::channel();
52+ let mut sender = Some(sender);
53+
54+ let _vdom = Vdom::new(
55+ &container,
56+ EventContainer::new("click", move || {
57+ sender
58+ .take()
59+ .expect_throw("should only call listener once")
60+ .send(())
61+ .expect_throw("should not have dropped the receiver");
62+ }),
63+ );
64+
65+ target(&container).click();
66+
67+ receiver.await.unwrap();
68+}
69+
70+#[wasm_bindgen_test]
71+async fn updated_listener_is_called() {
72+ let container = create_element("div");
73+
74+ let (first_sender, first_receiver) = futures::channel::oneshot::channel();
75+ let mut first_sender = Some(first_sender);
76+
77+ let vdom = Vdom::new(
78+ &container,
79+ EventContainer::new("click", move || {
80+ first_sender
81+ .take()
82+ .expect_throw("should only call listener once")
83+ .send("first")
84+ .expect_throw("should not have dropped the receiver");
85+ }),
86+ );
87+
88+ let (second_sender, second_receiver) = futures::channel::oneshot::channel();
89+ let mut second_sender = Some(second_sender);
90+
91+ vdom.weak()
92+ .set_component(Box::new(EventContainer::new("click", move || {
93+ second_sender
94+ .take()
95+ .expect_throw("should only call listener once")
96+ .send("second")
97+ .expect_throw("should not have dropped the receiver");
98+ })))
99+ .await
100+ .unwrap();
101+
102+ target(&container).click();
103+
104+ match select(first_receiver, second_receiver).await {
105+ Either::Left((Ok(_), _)) => panic!(),
106+ Either::Left((Err(_), second)) => assert_eq!(second.await, Ok("second")),
107+ Either::Right((Ok(msg), _)) => assert_eq!(msg, "second"),
108+ Either::Right((Err(_), _)) => panic!(),
109+ }
110+}
111+
112+struct ListensOnlyOnFirstRender {
113+ count: Cell<usize>,
114+ callback: Box<dyn FnMut()>,
115+}
116+
117+impl ListensOnlyOnFirstRender {
118+ fn new<F>(callback: F) -> ListensOnlyOnFirstRender
119+ where
120+ F: 'static + FnMut(),
121+ {
122+ ListensOnlyOnFirstRender {
123+ count: Cell::new(0),
124+ callback: Box::new(callback),
125+ }
126+ }
127+}
128+
129+impl<'a> Render<'a> for ListensOnlyOnFirstRender {
130+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
131+ use dodrio::builder::*;
132+
133+ let count = self.count.get();
134+ self.count.set(count + 1);
135+
136+ let mut elem = div(&cx).attr("id", "target");
137+ if count == 0 {
138+ elem = elem.on("click", |root, _scheduler, _event| {
139+ (root.unwrap_mut::<ListensOnlyOnFirstRender>().callback)();
140+ });
141+ }
142+ elem.finish()
143+ }
144+}
145+
146+#[wasm_bindgen_test]
147+async fn removed_listener_is_not_called() {
148+ let container = create_element("div");
149+
150+ let (outer_sender, outer_receiver) = futures::channel::oneshot::channel();
151+ let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
152+ let outer_listener = Closure::wrap(Box::new(move |_| {
153+ outer_sender
154+ .borrow_mut()
155+ .take()
156+ .expect_throw("should only invoke outer_listener once")
157+ .send("outer")
158+ .expect_throw("should not have dropped receiver");
159+ }) as Box<dyn FnMut(web_sys::Event)>);
160+
161+ container
162+ .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
163+ .unwrap();
164+
165+ let (vdom_sender, vdom_receiver) = futures::channel::oneshot::channel();
166+ let mut vdom_sender = Some(vdom_sender);
167+
168+ // Render a vdom with a listener into container.
169+ let vdom = Vdom::new(
170+ &container,
171+ ListensOnlyOnFirstRender::new(move || {
172+ vdom_sender
173+ .take()
174+ .expect_throw("should not invoke vdom listener more than once")
175+ .send("inner")
176+ .expect_throw("should not have dropped the receiver");
177+ }),
178+ );
179+
180+ // Re-render, so we aren't listening anymore.
181+ vdom.weak().render().await.unwrap();
182+
183+ target(&container).click();
184+
185+ // We should get our container's event handler fired, and not the unmounted
186+ // vdom's event handler.
187+ match select(outer_receiver, vdom_receiver).await {
188+ Either::Left((Ok(msg), _)) => assert_eq!(msg, ("outer")),
189+ Either::Left((Err(_), _)) => panic!(),
190+ Either::Right((Ok(_), _)) => panic!(),
191+ Either::Right((Err(_), outer)) => assert_eq!(outer.await, Ok("outer")),
192+ }
193+}
194+
195+#[wasm_bindgen_test]
196+async fn event_does_not_fire_after_unmounting() {
197+ let container = create_element("div");
198+
199+ let (outer_sender, outer_receiver) = futures::channel::oneshot::channel();
200+ let outer_sender = Rc::new(RefCell::new(Some(outer_sender)));
201+ let outer_listener = Closure::wrap(Box::new(move |_| {
202+ outer_sender
203+ .borrow_mut()
204+ .take()
205+ .expect_throw("should only invoke outer_listener once")
206+ .send("outer")
207+ .expect_throw("should not have dropped receiver");
208+ }) as Box<dyn FnMut(web_sys::Event)>);
209+
210+ container
211+ .add_event_listener_with_callback("click", outer_listener.as_ref().unchecked_ref())
212+ .unwrap();
213+
214+ let (vdom_sender, vdom_receiver) = futures::channel::oneshot::channel();
215+ let mut vdom_sender = Some(vdom_sender);
216+
217+ // Render a vdom with a listener into container.
218+ let vdom = Vdom::new(
219+ &container,
220+ EventContainer::new("click", move || {
221+ vdom_sender
222+ .take()
223+ .expect_throw("should not invoke vdom listener more than once")
224+ .send("vdom")
225+ .expect_throw("should not have dropped the receiver");
226+ }),
227+ );
228+
229+ // Grab a reference to the target before dropping clears the DOM.
230+ let target = target(&container);
231+
232+ // Unmount the vdom.
233+ drop(vdom);
234+
235+ // The target is no longer a child of the container, so re-append it so that
236+ // the click will bubble.
237+ container.append_child(&target).unwrap();
238+
239+ // Send a click to the target.
240+ target.click();
241+
242+ // We should get our container's event handler fired, and not the unmounted
243+ // vdom's event handler.
244+ match select(outer_receiver, vdom_receiver).await {
245+ Either::Left((Ok(msg), _)) => assert_eq!(msg, ("outer")),
246+ Either::Left((Err(_), _)) => panic!(),
247+ Either::Right((Ok(_), _)) => panic!(),
248+ Either::Right((Err(_), outer)) => assert_eq!(outer.await, Ok("outer")),
249+ }
250+}
tests/web/js_api.rsnew file+153
@@ -0,0 +1,153 @@
1+use super::{assert_rendered, create_element, RenderFn};
2+use dodrio::{builder::*, Node, Render, RenderContext, Vdom};
3+use dodrio_js_api::JsRender;
4+use futures::channel::oneshot;
5+use js_sys::Object;
6+use std::rc::Rc;
7+use wasm_bindgen::prelude::*;
8+use wasm_bindgen::JsCast;
9+use wasm_bindgen_test::*;
10+
11+pub struct WrapJs {
12+ inner: JsRender,
13+}
14+
15+impl WrapJs {
16+ pub fn new() -> (WrapJs, oneshot::Receiver<()>, Closure<dyn FnMut()>) {
17+ let (sender, receiver) = oneshot::channel();
18+
19+ let on_after_render = Closure::wrap(Box::new({
20+ let mut sender = Some(sender);
21+ move || {
22+ sender
23+ .take()
24+ .expect_throw("on_after_render should only be called once")
25+ .send(())
26+ .expect_throw("receiver should not have been dropped");
27+ }
28+ }) as Box<dyn FnMut()>);
29+
30+ let component = WrapJs {
31+ inner: JsRender::new(JsComponent::new(&on_after_render)),
32+ };
33+
34+ (component, receiver, on_after_render)
35+ }
36+}
37+
38+impl<'a> Render<'a> for WrapJs {
39+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
40+ div(&cx)
41+ .attr("class", "wrap-js")
42+ .children([self.inner.render(cx)])
43+ .finish()
44+ }
45+}
46+
47+#[wasm_bindgen]
48+extern "C" {
49+ #[wasm_bindgen(extends = Object)]
50+ #[derive(Clone, Debug)]
51+ type JsComponent;
52+
53+ #[wasm_bindgen(constructor)]
54+ fn new(on_after_render: &Closure<dyn FnMut()>) -> JsComponent;
55+}
56+
57+fn eval_js_rendering_component() {
58+ use std::sync::Once;
59+ static EVAL: Once = Once::new();
60+ EVAL.call_once(|| {
61+ super::init_logging();
62+ js_sys::eval(
63+ r#"
64+ window.JsComponent = class JsComponent {
65+ constructor(onAfterRender) {
66+ this.count = 0;
67+ this.onAfterRender = onAfterRender;
68+ }
69+
70+ render() {
71+ return {
72+ tagName: "span",
73+ attributes: [{ name: "class", value: "js-component" }],
74+ listeners: [{ on: "click", callback: this.onClick.bind(this) }],
75+ children: [
76+ "Here is some plain text",
77+ {
78+ tagName: "b",
79+ children: ["...and here is some bold text"]
80+ },
81+ String(this.count),
82+ ]
83+ };
84+ }
85+
86+ async onClick(vdom, event) {
87+ this.count++;
88+ await vdom.render();
89+ this.onAfterRender();
90+ }
91+ };
92+ "#,
93+ )
94+ .expect_throw("should eval JS component OK");
95+ });
96+}
97+
98+#[wasm_bindgen_test]
99+async fn can_use_js_rendering_components() {
100+ eval_js_rendering_component();
101+
102+ let container = create_element("div");
103+ let (component, receiver, _closure) = WrapJs::new();
104+ let _vdom = Rc::new(Vdom::new(&container, component));
105+
106+ assert_rendered(
107+ &container,
108+ &RenderFn(|cx| {
109+ div(&cx)
110+ .attr("class", "wrap-js")
111+ .children([span(&cx)
112+ .attr("class", "js-component")
113+ .children([
114+ text("Here is some plain text"),
115+ b(&cx)
116+ .children([text("...and here is some bold text")])
117+ .finish(),
118+ text("0"),
119+ ])
120+ .finish()])
121+ .finish()
122+ }),
123+ );
124+
125+ container
126+ .query_selector(".js-component")
127+ .expect_throw("should querySelector OK")
128+ .expect_throw("should find `.js-component` element OK")
129+ .dyn_into::<web_sys::HtmlElement>()
130+ .expect_throw("should be an `HTMLElement` object")
131+ .click();
132+
133+ receiver.await.unwrap();
134+
135+ assert_rendered(
136+ &container,
137+ &RenderFn(|cx| {
138+ div(&cx)
139+ .attr("class", "wrap-js")
140+ .children([span(&cx)
141+ .attr("class", "js-component")
142+ .children([
143+ text("Here is some plain text"),
144+ b(&cx)
145+ .children([text("...and here is some bold text")])
146+ .finish(),
147+ text("1"),
148+ ])
149+ .finish()])
150+ .finish()
151+ }),
152+ );
153+}
tests/web/keyed.jsnew file+25
@@ -0,0 +1,25 @@
1+export function saveKeyedElements(container) {
2+ const saved = new Map;
3+ const children = container.querySelectorAll(".keyed");
4+ for (const child of children) {
5+ saved.set(child.id, child);
6+ }
7+ return saved;
8+}
9+
10+function assertEq(a, b, msg) {
11+ if (a !== b) {
12+ throw new Error(`assertEq failed: ${a} !== ${b}; ${msg}`);
13+ }
14+}
15+
16+export function checkKeyedElements(container, saved) {
17+ const children = container.querySelectorAll(".keyed");
18+ for (const child of children) {
19+ console.log(`checking child=${child.outerHTML}`);
20+ const original = saved.get(child.id);
21+ if (original) {
22+ assertEq(original, child, `did not preserve child with key=${child.id}`);
23+ }
24+ }
25+}
tests/web/keyed.rsnew file+269
@@ -0,0 +1,269 @@
1+use crate::{assert_rendered, create_element};
2+use dodrio::{builder::*, bumpalo, Node, Render, RenderContext, Vdom};
3+use log::*;
4+use std::rc::Rc;
5+use wasm_bindgen::prelude::*;
6+use wasm_bindgen_test::*;
7+
8+struct Keyed(u16);
9+
10+impl<'a> Render<'a> for Keyed {
11+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
12+ let key = bumpalo::format!(in cx.bump, "{}", self.0).into_bump_str();
13+ div(&cx)
14+ .attr("class", "keyed")
15+ .attr("id", key)
16+ .key(self.0 as u32)
17+ .finish()
18+ }
19+}
20+
21+fn keyed<'a, Keys>(cx: &mut RenderContext<'a>, keys: Keys) -> Node<'a>
22+where
23+ Keys: AsRef<[u16]>,
24+{
25+ let mut parent = div(&cx).attr("class", "parent");
26+
27+ for &k in keys.as_ref() {
28+ parent = parent.child(Keyed(k).render(cx));
29+ }
30+
31+ parent.finish()
32+}
33+
34+async fn assert_keyed<Before, After>(before: Before, after: After) -> Result<(), JsValue>
35+where
36+ Before: 'static + for<'a> Render<'a>,
37+ After: 'static + for<'a> Render<'a>,
38+{
39+ #[wasm_bindgen(module = "/tests/web/keyed.js")]
40+ extern "C" {
41+ #[wasm_bindgen(js_name = saveKeyedElements)]
42+ fn save_keyed_elements(container: &web_sys::Element) -> JsValue;
43+
44+ #[wasm_bindgen(js_name = checkKeyedElements)]
45+ fn check_keyed_elements(container: &web_sys::Element, saved: JsValue);
46+ }
47+
48+ let container = create_element("div");
49+
50+ let before = Rc::new(before);
51+ let after = Rc::new(after);
52+
53+ debug!("====== Rendering the *before* DOM into the physical DOM ======");
54+ let vdom1 = Rc::new(Vdom::new(&container, before.clone()));
55+ let _vdom2 = vdom1.clone();
56+ let saved = save_keyed_elements(&container);
57+
58+ debug!("====== Checking the *before* DOM against the physical DOM ======");
59+ assert_rendered(&container, &before);
60+
61+ debug!("====== Rendering the *after* DOM into the physical DOM ======");
62+ let weak = vdom1.weak();
63+ weak.set_component(Box::new(after.clone()))
64+ .await
65+ .map_err(|e| e.to_string())?;
66+
67+ debug!("====== Checking the *after* DOM against the physical DOM ======");
68+ assert_rendered(&container, &after);
69+ check_keyed_elements(&container, saved);
70+
71+ Ok(())
72+}
73+
74+macro_rules! keyed_tests {
75+ ( $(
76+ $name:ident {
77+ before($before_cx:ident) {
78+ $( $before:tt )*
79+ }
80+ after($after_cx:ident) {
81+ $( $after:tt )*
82+ }
83+ }
84+ )* ) => {
85+ $(
86+ #[wasm_bindgen_test]
87+ async fn $name() {
88+ use crate::RenderFn;
89+ log::debug!("############### {} ###############", stringify!($name));
90+ assert_keyed(
91+ RenderFn(|$before_cx| { $( $before )* }),
92+ RenderFn(|$after_cx| { $( $after )* }),
93+ )
94+ .await
95+ .unwrap()
96+ }
97+ )*
98+ }
99+}
100+
101+keyed_tests! {
102+ same_order {
103+ before(cx) {
104+ keyed(cx, [1, 2, 3])
105+ }
106+ after(cx) {
107+ keyed(cx, [1, 2, 3])
108+ }
109+ }
110+
111+ same_order_append {
112+ before(cx) {
113+ keyed(cx, [1])
114+ }
115+ after(cx) {
116+ keyed(cx, [1, 2])
117+ }
118+ }
119+
120+ same_order_delete {
121+ before(cx) {
122+ keyed(cx, [1, 2, 3])
123+ }
124+ after(cx) {
125+ keyed(cx, [1, 2])
126+ }
127+ }
128+
129+ same_suffix {
130+ before(cx) {
131+ keyed(cx, [1, 2, 3])
132+ }
133+ after(cx) {
134+ keyed(cx, [4, 2, 3])
135+ }
136+ }
137+
138+ same_prefix_and_suffix_reorder_middle {
139+ before(cx) {
140+ keyed(cx, [1, 2, 3, 4])
141+ }
142+ after(cx) {
143+ keyed(cx, [1, 3, 2, 4])
144+ }
145+ }
146+
147+ same_prefix_and_suffix_new_middle {
148+ before(cx) {
149+ keyed(cx, [2, 3, 4, 5])
150+ }
151+ after(cx) {
152+ keyed(cx, [2, 7, 8, 5])
153+ }
154+ }
155+
156+ reverse_order {
157+ before(cx) {
158+ keyed(cx, [1, 2, 3])
159+ }
160+ after(cx) {
161+ keyed(cx, [3, 2, 1])
162+ }
163+ }
164+
165+ no_shared_keys {
166+ before(cx) {
167+ keyed(cx, [1, 2])
168+ }
169+ after(cx) {
170+ keyed(cx, [3, 4])
171+ }
172+ }
173+
174+ new_keys_in_middle {
175+ before(cx) {
176+ keyed(cx, [1, 2])
177+ }
178+ after(cx) {
179+ keyed(cx, [1, 3, 4, 2])
180+ }
181+ }
182+
183+ new_keys_at_start_and_end {
184+ before(cx) {
185+ keyed(cx, [1, 2, 3])
186+ }
187+ after(cx) {
188+ keyed(cx, [4, 1, 2, 3, 5])
189+ }
190+ }
191+
192+ delete_prefix {
193+ before(cx) {
194+ keyed(cx, [1, 2, 3])
195+ }
196+ after(cx) {
197+ keyed(cx, [2, 3])
198+ }
199+ }
200+
201+ delete_suffix {
202+ before(cx) {
203+ keyed(cx, [1, 2, 3])
204+ }
205+ after(cx) {
206+ keyed(cx, [1, 2])
207+ }
208+ }
209+
210+ delete_middle {
211+ before(cx) {
212+ keyed(cx, [1, 2, 3])
213+ }
214+ after(cx) {
215+ keyed(cx, [1, 3])
216+ }
217+ }
218+
219+ nested_keyed_children {
220+ before(cx) {
221+ ul(&cx)
222+ .children([
223+ li(&cx)
224+ .key(1)
225+ .children([
226+ keyed(cx, [2, 3, 4])
227+ ])
228+ .finish(),
229+ li(&cx)
230+ .key(5)
231+ .children([
232+ keyed(cx, [6, 7, 8])
233+ ])
234+ .finish(),
235+ li(&cx)
236+ .key(9)
237+ .children([
238+ keyed(cx, [10, 11, 12])
239+ ])
240+ .finish(),
241+ ])
242+ .finish()
243+ }
244+ after(cx) {
245+ ul(&cx)
246+ .children([
247+ li(&cx)
248+ .key(9)
249+ .children([
250+ keyed(cx, [12, 11, 10])
251+ ])
252+ .finish(),
253+ li(&cx)
254+ .key(5)
255+ .children([
256+ keyed(cx, [8, 7, 6])
257+ ])
258+ .finish(),
259+ li(&cx)
260+ .key(1)
261+ .children([
262+ keyed(cx, [4, 3, 2])
263+ ])
264+ .finish(),
265+ ])
266+ .finish()
267+ }
268+ }
269+}
tests/web/main.rsnew file+226
@@ -0,0 +1,226 @@
1+//! Test suite for the Web and headless browsers.
2+
3+#![cfg(all(feature = "xxx-unstable-internal-use-only", target_arch = "wasm32"))]
4+
5+use bumpalo::Bump;
6+use dodrio::{
7+ Attribute, CachedSet, ElementNode, Node, NodeKind, Render, RenderContext, TextNode, Vdom,
8+};
9+use fxhash::FxHashMap;
10+use log::*;
11+use std::cell::RefCell;
12+use std::rc::Rc;
13+use wasm_bindgen::prelude::*;
14+use wasm_bindgen::JsCast;
15+use wasm_bindgen_test::*;
16+
17+wasm_bindgen_test_configure!(run_in_browser);
18+
19+pub mod cached;
20+pub mod events;
21+pub mod js_api;
22+pub mod keyed;
23+pub mod render;
24+
25+pub fn window() -> web_sys::Window {
26+ web_sys::window().expect("no global `window` exists")
27+}
28+
29+pub fn document() -> web_sys::Document {
30+ window()
31+ .document()
32+ .expect("should have a document on window")
33+}
34+
35+pub fn create_element(tag: &str) -> web_sys::Element {
36+ init_logging();
37+ document()
38+ .create_element(tag)
39+ .expect("should create element OK")
40+}
41+
42+/// Ensure that logs go to the devtools console.
43+pub fn init_logging() {
44+ use std::sync::Once;
45+ static START: Once = Once::new();
46+ START.call_once(|| {
47+ console_log::init_with_level(Level::Trace).expect("could not initialize console_log");
48+ });
49+}
50+
51+/// Assert that the `container` contains the physical DOM tree that matches
52+/// `r`'s rendered virtual DOM.
53+pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &R) {
54+ init_logging();
55+
56+ let cached_set = &RefCell::new(CachedSet::default());
57+ let bump = &Bump::new();
58+ let templates = &mut FxHashMap::default();
59+ let cx = &mut RenderContext::new(bump, cached_set, templates);
60+ let node = r.render(cx);
61+ let child = container
62+ .first_child()
63+ .expect("container does not have anything rendered into it?");
64+
65+ let cached_set = cached_set.borrow();
66+ check_node(&cached_set, &child, &node);
67+
68+ fn stringify_actual_node(n: &web_sys::Node) -> String {
69+ if let Some(el) = n.dyn_ref::<web_sys::Element>() {
70+ el.outer_html()
71+ } else {
72+ format!("#text({:?})", n.text_content())
73+ }
74+ }
75+
76+ fn check_node(cached_set: &CachedSet, actual: &web_sys::Node, expected: &Node) {
77+ debug!("check_render:");
78+ debug!(" actual = {}", stringify_actual_node(&actual));
79+ debug!(" expected = {:#?}", expected);
80+ match expected.kind {
81+ NodeKind::Text(TextNode { text }) => {
82+ assert_eq!(
83+ actual.node_name().to_uppercase(),
84+ "#TEXT",
85+ "actual.node_name() == #TEXT"
86+ );
87+ assert_eq!(
88+ actual.text_content().unwrap_or_default(),
89+ text,
90+ "actual.text_content() == expected.text()"
91+ );
92+ }
93+ NodeKind::Element(&ElementNode {
94+ tag_name,
95+ attributes,
96+ children,
97+ namespace,
98+ ..
99+ }) => {
100+ assert_eq!(
101+ actual.node_name().to_uppercase(),
102+ tag_name.to_uppercase(),
103+ "actual.node_name() == expected.tag_name()"
104+ );
105+ let actual = actual
106+ .dyn_ref::<web_sys::Element>()
107+ .expect("`actual` should be an `Element`");
108+ check_attributes(actual.attributes(), attributes);
109+ check_children(cached_set, actual.child_nodes(), children);
110+ if let Some(namespace) = namespace {
111+ assert_eq!(actual.namespace_uri(), Some(namespace.into()))
112+ }
113+ }
114+ NodeKind::Cached(ref c) => {
115+ let (expected, _template) = cached_set.get(c.id);
116+ check_node(cached_set, actual, &expected);
117+ }
118+ }
119+ }
120+
121+ fn check_attributes(actual: web_sys::NamedNodeMap, expected: &[Attribute]) {
122+ assert_eq!(
123+ actual.length(),
124+ expected.len() as u32,
125+ "actual's number of attributes == expected's number of attributes"
126+ );
127+ for attr in expected {
128+ let actual_attr = actual
129+ .get_named_item(attr.name())
130+ .expect(&format!("should have attribute \"{}\"", attr.name()));
131+ assert_eq!(
132+ actual_attr.value(),
133+ attr.value(),
134+ "actual attr value == expected attr value for attr \"{}\"",
135+ attr.name()
136+ );
137+ }
138+ }
139+
140+ fn check_children(cached_set: &CachedSet, actual: web_sys::NodeList, expected: &[Node]) {
141+ assert_eq!(
142+ actual.length(),
143+ expected.len() as u32,
144+ "actual children length == expected children length"
145+ );
146+ for (i, child) in expected.iter().enumerate() {
147+ let actual_child = actual.item(i as u32).unwrap();
148+ check_node(cached_set, &actual_child, child);
149+ }
150+ }
151+}
152+
153+/// Use the function `F` to render.
154+pub struct RenderFn<F>(F)
155+where
156+ F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>;
157+
158+impl<'a, F> Render<'a> for RenderFn<F>
159+where
160+ F: for<'b> Fn(&mut RenderContext<'b>) -> Node<'b>,
161+{
162+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
163+ (self.0)(cx)
164+ }
165+}
166+
167+/// Assert that if we start by rendering the `before` virtual DOM tree into a
168+/// physical DOM tree, and then diff it with the `after` virtual DOM tree, then
169+/// the physical DOM tree correctly matches `after`.
170+pub async fn assert_before_after<R, S>(before: R, after: S) -> Result<(), JsValue>
171+where
172+ R: 'static + for<'a> Render<'a>,
173+ S: 'static + for<'a> Render<'a>,
174+{
175+ let container = create_element("div");
176+
177+ let before = Rc::new(before);
178+ let after = Rc::new(after);
179+
180+ debug!("====== Rendering the *before* DOM into the physical DOM ======");
181+ let vdom1 = Rc::new(Vdom::new(&container, before.clone()));
182+ let _vdom2 = vdom1.clone();
183+
184+ debug!("====== Checking the *before* DOM against the physical DOM ======");
185+ assert_rendered(&container, &before);
186+
187+ debug!("====== Rendering the *after* DOM into the physical DOM ======");
188+ let weak = vdom1.weak();
189+ weak.set_component(Box::new(after.clone()))
190+ .await
191+ .map_err(|e| e.to_string())?;
192+
193+ debug!("====== Checking the *after* DOM against the physical DOM ======");
194+ assert_rendered(&container, &after);
195+
196+ Ok(())
197+}
198+
199+/// A helper macro for declaring a bunch of `assert_before_after` tests.
200+#[macro_export]
201+macro_rules! before_after {
202+ ( $(
203+ $name:ident {
204+ before($before_bump:ident) {
205+ $( $before:tt )*
206+ }
207+ after($after_bump:ident) {
208+ $( $after:tt )*
209+ }
210+ }
211+ )* ) => {
212+ $(
213+ #[wasm_bindgen_test]
214+ async fn $name() {
215+ use crate::{assert_before_after, RenderFn};
216+ log::debug!("############### {} ###############", stringify!($name));
217+ assert_before_after(
218+ RenderFn(|$before_bump| { $( $before )* }),
219+ RenderFn(|$after_bump| { $( $after )* })
220+ )
221+ .await
222+ .unwrap()
223+ }
224+ )*
225+ }
226+}
tests/web/render.rsnew file+343
@@ -0,0 +1,343 @@
1+use super::{assert_rendered, before_after, create_element, RenderFn};
2+use dodrio::{builder::*, bumpalo::collections::String, Node, Render, RenderContext, Vdom};
3+use std::rc::Rc;
4+use wasm_bindgen::{ JsCast};
5+use wasm_bindgen_test::*;
6+
7+#[wasm_bindgen_test]
8+fn render_initial_text() {
9+ let hello = Rc::new(RenderFn(|_cx| text("hello")));
10+
11+ let container = create_element("div");
12+ let _vdom = Vdom::new(&container, hello.clone());
13+ assert_rendered(&container, &hello);
14+}
15+
16+#[wasm_bindgen_test]
17+fn render_initial_node() {
18+ let hello = Rc::new(RenderFn(|cx| {
19+ div(&cx)
20+ .attr("id", "hello-world")
21+ .children([text("Hello "), span(&cx).child(text("World!")).finish()])
22+ .finish()
23+ }));
24+
25+ let container = create_element("div");
26+ let _vdom = Vdom::new(&container, hello.clone());
27+ assert_rendered(&container, &hello);
28+}
29+
30+#[wasm_bindgen_test]
31+fn container_is_emptied_upon_drop() {
32+ let container = create_element("div");
33+ let vdom = Vdom::new(&container, RenderFn(|_cx| text("blah")));
34+ drop(vdom);
35+ assert!(container.first_child().is_none());
36+}
37+
38+/// Renders a child with a lifetime scoped to the RenderContext bump arena.
39+#[wasm_bindgen_test]
40+fn render_bump_scoped_node() {
41+ struct Child<'a> {
42+ name: &'a str,
43+ }
44+
45+ impl<'a> Render<'a> for Child<'a> {
46+ fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
47+ text(self.name)
48+ }
49+ }
50+
51+ struct Parent;
52+
53+ impl<'a> Render<'a> for Parent {
54+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
55+ let child_name = String::from_str_in("child", cx.bump).into_bump_str();
56+
57+ div(&cx)
58+ .children([Child { name: child_name }.render(cx)])
59+ .finish()
60+ }
61+ }
62+
63+ let parent = Rc::new(RenderFn(|cx| {
64+ Parent.render(cx)
65+ }));
66+
67+ let container = create_element("div");
68+ let _vdom = Vdom::new(&container, parent.clone());
69+
70+ assert_rendered(&container, &parent);
71+}
72+
73+/// Originally, dodrio would use the className property for SVGs.
74+///
75+/// This is problematic because when SVG elements are created, the className is flagged as a read
76+/// only property, so setting it causes an exception to be thrown. Here's an example of how this
77+/// happens:
78+///
79+/// let elem = web_sys::window()
80+/// .unwrap()
81+/// .document()
82+/// .unwrap()
83+/// .create_element_ns(Some("http://www.w3.org/2000/svg"), "svg")
84+/// .unwrap();
85+///
86+/// elem.set_class_name("does-not-work");
87+///
88+/// -----------------------------------------------------------------------------------------------
89+///
90+/// wasm-bindgen: imported JS function that was not marked as `catch` threw an error:
91+/// setting getter-only property "className"
92+///
93+/// -----------------------------------------------------------------------------------------------
94+///
95+/// Now, dodrio passes the 'class' attribute of all namespaced elements into set_attribute. This
96+/// satisfies the restrictions on SVG and keeps the optimized path for non-namespaced elements
97+#[wasm_bindgen_test(async)]
98+async fn test_svg_set_class() {
99+ let container = create_element("div");
100+
101+
102+ let valid_svg = Rc::new(RenderFn(|cx| {
103+ ElementBuilder::new(cx.bump, "svg")
104+ .namespace(Some("http://www.w3.org/2000/svg"))
105+ .attr("class", "works")
106+ .finish()
107+ }));
108+
109+ let vdom = Vdom::new(&container, valid_svg.clone());
110+ let weak = vdom.weak();
111+
112+ weak.render().await.unwrap();
113+
114+ assert_eq!(
115+ "works",
116+ container.first_child()
117+ .expect("unable to get svg")
118+ .dyn_ref::<web_sys::Element>()
119+ .expect("svg should be an element")
120+ .get_attribute("class")
121+ .expect("unable to get 'class' of svg")
122+ );
123+}
124+
125+before_after! {
126+ same_text {
127+ before(_cx) {
128+ text("hello")
129+ }
130+ after(_cx) {
131+ text("hello")
132+ }
133+ }
134+
135+ update_text {
136+ before(_cx) {
137+ text("before")
138+ }
139+ after(_cx) {
140+ text("after")
141+ }
142+ }
143+
144+ replace_text_with_elem {
145+ before(_cx) {
146+ text("before")
147+ }
148+ after(cx) {
149+ div(&cx).finish()
150+ }
151+ }
152+
153+ replace_elem_with_text {
154+ before(cx) {
155+ div(&cx).finish()
156+ }
157+ after(_cx) {
158+ text("before")
159+ }
160+ }
161+
162+ same_elem {
163+ before(cx) {
164+ div(&cx).finish()
165+ }
166+ after(cx) {
167+ div(&cx).finish()
168+ }
169+ }
170+
171+ elems_with_different_tag_names {
172+ before(cx) {
173+ span(&cx).finish()
174+ }
175+ after(cx) {
176+ div(&cx).finish()
177+ }
178+ }
179+
180+ same_tag_name_update_attribute {
181+ before(cx) {
182+ div(&cx).attr("value", "1").finish()
183+ }
184+ after(cx) {
185+ div(&cx).attr("value", "2").finish()
186+ }
187+ }
188+
189+ same_tag_name_remove_attribute {
190+ before(cx) {
191+ div(&cx).attr("value", "1").finish()
192+ }
193+ after(cx) {
194+ div(&cx).finish()
195+ }
196+ }
197+
198+ same_tag_name_add_attribute {
199+ before(cx) {
200+ div(&cx).finish()
201+ }
202+ after(cx) {
203+ div(&cx).attr("value", "2").finish()
204+ }
205+ }
206+
207+ same_tag_name_many_attributes {
208+ before(cx) {
209+ div(&cx)
210+ .attr("before-1", "1")
211+ .attr("shared-1", "1")
212+ .attr("modified-1", "1")
213+ .attr("before-2", "2")
214+ .attr("shared-2", "2")
215+ .attr("modified-2", "2")
216+ .attr("before-3", "3")
217+ .attr("shared-3", "3")
218+ .attr("modified-3", "3")
219+ .finish()
220+ }
221+ after(cx) {
222+ div(&cx)
223+ .attr("after-1", "1")
224+ .attr("shared-1", "1")
225+ .attr("modified-1", "100")
226+ .attr("after-2", "2")
227+ .attr("shared-2", "2")
228+ .attr("modified-2", "200")
229+ .attr("after-3", "3")
230+ .attr("shared-3", "3")
231+ .attr("modified-3", "300")
232+ .finish()
233+ }
234+ }
235+
236+ same_tag_same_children {
237+ before(cx) {
238+ div(&cx).child(text("child")).finish()
239+ }
240+ after(cx) {
241+ div(&cx).child(text("child")).finish()
242+ }
243+ }
244+
245+ same_tag_update_child {
246+ before(cx) {
247+ div(&cx).child(text("before")).finish()
248+ }
249+ after(cx) {
250+ div(&cx).child(text("after")).finish()
251+ }
252+ }
253+
254+ same_tag_add_child {
255+ before(cx) {
256+ div(&cx).finish()
257+ }
258+ after(cx) {
259+ div(&cx).child(text("child")).finish()
260+ }
261+ }
262+
263+ same_tag_remove_child {
264+ before(cx) {
265+ div(&cx).child(text("child")).finish()
266+ }
267+ after(cx) {
268+ div(&cx).finish()
269+ }
270+ }
271+
272+ same_tag_update_many_children {
273+ before(cx) {
274+ div(&cx)
275+ .children([
276+ div(&cx).finish(),
277+ span(&cx).finish(),
278+ p(&cx).finish(),
279+ ])
280+ .finish()
281+ }
282+ after(cx) {
283+ div(&cx)
284+ .children([
285+ span(&cx).finish(),
286+ p(&cx).finish(),
287+ div(&cx).finish(),
288+ ])
289+ .finish()
290+ }
291+ }
292+
293+ same_tag_remove_many_children {
294+ before(cx) {
295+ div(&cx)
296+ .children([
297+ div(&cx).finish(),
298+ span(&cx).finish(),
299+ p(&cx).finish(),
300+ ])
301+ .finish()
302+ }
303+ after(cx) {
304+ div(&cx)
305+ .children([
306+ div(&cx).finish(),
307+ ])
308+ .finish()
309+ }
310+ }
311+
312+ same_tag_add_many_children {
313+ before(cx) {
314+ div(&cx)
315+ .children([
316+ div(&cx).finish(),
317+ ])
318+ .finish()
319+ }
320+ after(cx) {
321+ div(&cx)
322+ .children([
323+ div(&cx).finish(),
324+ span(&cx).finish(),
325+ p(&cx).finish(),
326+ ])
327+ .finish()
328+ }
329+ }
330+
331+ same_tag_different_namespace {
332+ before(cx) {
333+ div(&cx)
334+ .namespace(Some("http://example.com"))
335+ .finish()
336+ }
337+ after(cx) {
338+ div(&cx)
339+ .namespace(Some("http://example.net"))
340+ .finish()
341+ }
342+ }
343+}