diff --git a/.gitbook.yaml b/.gitbook.yaml new file mode 100644 index 00000000000..2a894db64e2 --- /dev/null +++ b/.gitbook.yaml @@ -0,0 +1 @@ +root: ./docs/src diff --git a/.github/workflows/doctests.yml b/.github/workflows/doctests.yml new file mode 100644 index 00000000000..4da9ec61096 --- /dev/null +++ b/.github/workflows/doctests.yml @@ -0,0 +1,27 @@ +name: Documentation tests +on: + push: + paths: + - "docs/**/*" + pull_request: + paths: + - "docs/**/*" + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + - name: Run tests + run: cd docs && cargo test + check-spelling: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check spelling + run: bash ci/spellcheck.sh list diff --git a/.gitignore b/.gitignore index 3c6f327e589..061f0d74763 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ orig.* /.idea /.vscode /cmake-build-debug +/docs/target \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b1d8ad6dc2a..ea5d0ee00b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,3 +57,4 @@ members = [ "examples/two_apps", "examples/webgl", ] +exclude=["docs"] \ No newline at end of file diff --git a/README.md b/README.md index bb5302471a0..1350a472f3c 100644 --- a/README.md +++ b/README.md @@ -97,4 +97,4 @@ Support this project with your organization. Your logo will show up here with a - + \ No newline at end of file diff --git a/ci/dictionary.txt b/ci/dictionary.txt new file mode 100644 index 00000000000..d40d130f076 --- /dev/null +++ b/ci/dictionary.txt @@ -0,0 +1,17 @@ +personal_ws-1.1 en 0 utf-8 +APIs +bindgen +interoperable +interoperability +libs +Libs +minimising +Optimisations +roadmap +README +stdweb +html +Roadmap +wasm +Wasm +WebAssembly diff --git a/ci/spellcheck.sh b/ci/spellcheck.sh new file mode 100755 index 00000000000..03d48d784b2 --- /dev/null +++ b/ci/spellcheck.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +aspell --version + +# Taken from the Rust Book (https://github.com/rust-lang/book/) + +# Checks project Markdown files for spelling mistakes. + +# Notes: + +# This script needs dictionary file ($dict_filename) with project-specific +# valid words. If this file is missing, first invocation of a script generates +# a file of words considered typos at the moment. User should remove real typos +# from this file and leave only valid words. When script generates false +# positive after source modification, new valid word should be added +# to dictionary file. + +# Default mode of this script is interactive. Each source file is scanned for +# typos. aspell opens window, suggesting fixes for each found typo. Original +# files with errors will be backed up to files with format "filename.md.bak". + +# When running in CI, this script should be run in "list" mode (pass "list" +# as first argument). In this mode script scans all files and reports found +# errors. Exit code in this case depends on scan result: +# 1 if any errors found, +# 0 if all is clear. + +# Script skips words with length less than or equal to 3. This helps to avoid +# some false positives. + +# We can consider skipping source code in markdown files (```code```) to reduce +# rate of false positives, but then we lose ability to detect typos in code +# comments/strings etc. + +shopt -s nullglob + +dict_filename=./ci/dictionary.txt +markdown_sources=(./docs/**/*.md) +mode="check" + +# aspell repeatedly modifies the personal dictionary for some reason, +# so we should use a copy of our dictionary. +dict_path="/tmp/dictionary.txt" + +if [[ "$1" == "list" ]]; then + mode="list" +fi + +# Error if running in list (CI) mode and there isn't a dictionary file; +# creating one in CI won't do any good :( +if [[ "$mode" == "list" && ! -f "$dict_filename" ]]; then + echo "No dictionary file found! A dictionary file is required in CI!" + exit 1 +fi + +if [[ ! -f "$dict_filename" ]]; then + # Pre-check mode: generates dictionary of words aspell consider typos. + # After user validates that this file contains only valid words, we can + # look for typos using this dictionary and some default aspell dictionary. + echo "Scanning files to generate dictionary file '$dict_filename'." + echo "Please check that it doesn't contain any misspellings." + + echo "personal_ws-1.1 en 0 utf-8" > "$dict_filename" + cat "${markdown_sources[@]}" | aspell --ignore 3 list | sort -u >> "$dict_filename" +elif [[ "$mode" == "list" ]]; then + # List (default) mode: scan all files, report errors. + declare -i retval=0 + + cp "$dict_filename" "$dict_path" + + if [ ! -f $dict_path ]; then + retval=1 + exit "$retval" + fi + + for fname in "${markdown_sources[@]}"; do + command=$(aspell --ignore 3 --personal="$dict_path" "$mode" < "$fname") + if [[ -n "$command" ]]; then + for error in $command; do + # FIXME: find more correct way to get line number + # (ideally from aspell). Now it can make some false positives, + # because it is just a grep. + grep --with-filename --line-number --color=always "$error" "$fname" + done + retval=1 + fi + done + exit "$retval" +elif [[ "$mode" == "check" ]]; then + # Interactive mode: fix typos. + cp "$dict_filename" "$dict_path" + + if [ ! -f $dict_path ]; then + retval=1 + exit "$retval" + fi + + for fname in "${markdown_sources[@]}"; do + aspell --ignore 3 --dont-backup --personal="$dict_path" "$mode" "$fname" + done +fi \ No newline at end of file diff --git a/docs/Cargo.toml b/docs/Cargo.toml new file mode 100644 index 00000000000..734671b1c6b --- /dev/null +++ b/docs/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "docs" +version = "0.1.0" +authors = ["Yew Maintainers "] +edition = "2018" + +[dependencies] +doc-comment = "0.3.3" +wasm-bindgen = "0.2" +wasm-bindgen-test = "0.3.0" +wee_alloc = "0.4" +yew = { version = "0.16.0", features = ["wasm_test"] } \ No newline at end of file diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 00000000000..0e259d42c99 --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..7bc47dc622f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,54 @@ +# Contributing to the documentation + +Firstly, thanks for considering contributing to Yew. We're a friendly community of humans who +collaborate to build (hopefully) awesome software. We try to write software which is easy to use so +we ask that you follow a few guidelines when contributing – these are laid out in this document. + +Note that this document is about *contributing documentation* not contributing code and only +applies to the contents of the `docs` folder – not any other parts of the project. + +## Can I just submit a PR or do I need to create an issue first? + +PRs not attached to previously created issues are welcome *if* they're a small change. This could +be something like fixing a small grammar mistake or a typo. + +If your PR is something bigger create an issue beforehand. This will save everyone time and effort: + +1. Multiple people don't end up submitting duplicate PRs doing the same thing. +2. People have a chance to submit feedback on an idea *before* someone does a lot of work on it. +3. It's easy to track progress on the development of the documentation. + +## Spelling and grammar + +We recognise that not everyone who contributes to Yew will have "perfect" grammar or be a native +English speaker. We're all human; everyone makes mistakes and nobody will look down on you if you +make typos or grammar mistakes (we'll just fix them, merge PRs and move on with life). + +To help catch spelling mistakes, we use a spellchecking script which originally came from the Rust +Book. If it picks up a spelling "mistake" which isn't actually a mistake, then please add it to the +list in `ci/dictionary.txt` (in alphabetically sorted order). + +If in doubt about spelling, grammar or style you might find it useful to consult the +[Microsoft Style Guide](https://docs.microsoft.com/style-guide/) which we sometimes use as a handy +reference. + +## Line wrap +Having really long lines makes it hard to review code and see differences between versions. To +solve this problem all lines should be wrapped at 100 characters. + +## Building the book locally + +Note that Yew uses Gitbook which renders differently from mdbook. However the latest Gitbook is +not compatible with gitbook-cli :( + +### Install mdbook: + +```bash +$ cargo install mdbook +``` + +### Render and view the book: + +```bash +$ mdbook serve +``` \ No newline at end of file diff --git a/docs/build.rs b/docs/build.rs new file mode 100644 index 00000000000..9cd8eb31d8f --- /dev/null +++ b/docs/build.rs @@ -0,0 +1,21 @@ +use std::env; +use std::error::Error; +use std::fs; +use std::path::Path; + +fn main() -> Result<(), Box> { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let src_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); + let out_path = Path::new(&out_dir); + + let original_path = src_path.join("getting-started/build-a-sample-app.md"); + let original_str = fs::read_to_string(original_path)?.replace("fn main", "fn _main"); + let sanitized_path = out_path.join("getting-started/build-a-sample-app.md"); + let _ignore = fs::create_dir(&out_path.join("getting-started")); + fs::write(sanitized_path, original_str)?; + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/getting-started-build-a-simple-app.md"); + + Ok(()) +} diff --git a/docs/src/.gitbook/assets/yew.svg b/docs/src/.gitbook/assets/yew.svg new file mode 100644 index 00000000000..35a471408f5 --- /dev/null +++ b/docs/src/.gitbook/assets/yew.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/src/README.md b/docs/src/README.md new file mode 100644 index 00000000000..ca220093d55 --- /dev/null +++ b/docs/src/README.md @@ -0,0 +1,81 @@ +# Introduction + +## What is Yew? + +**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for creating multi-threaded +front-end web apps with [WebAssembly](https://webassembly.org/). + +* It features a **component-based** framework which makes it easy to create interactive UIs. +Developers who have experience with frameworks like [React](https://reactjs.org/) and +[Elm](https://elm-lang.org/) should feel quite at home when using Yew. +* It has **great performance** by minimising DOM API calls and by helping developers easily offload +processing to background web workers. +* It supports **JavaScript interoperability**, allowing developers to leverage NPM packages and +integrate with existing JavaScript applications. + +### Join Us 😊 + +* You can report bugs and discuss features on the +[GitHub issues page](https://github.com/yewstack/yew/issues) +* We ❤️ pull requests. Check out +[good first issues](https://github.com/yewstack/yew/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) +if you'd like to help out! +* Our [Discord chat](https://discord.gg/VQck8X4) is very active and is a great place to ask +questions. + +![Our community is thriving!](https://img.shields.io/github/stars/yewstack/yew?color=009A5B&label=Github%20stars) + +### Ready to dive in? + +Click the link below to learn how to build your first Yew app and learn from community example +projects. + +{% page-ref page="getting-started/project-setup/" %} + +### **Still not convinced?** + +This project is built on cutting edge technology and is great for developers who like to develop the +foundational projects of tomorrow. Here are some reasons why we believe that frameworks like Yew are +the future of web development. + +#### **Wait, why WebAssembly?** + +WebAssembly _\(Wasm\)_ is a portable low-level language that Rust can compile into. It runs at +native speeds in the browser and is interoperable with JavaScript and supported in all major +browsers. For ideas on how to get the most out of WebAssembly for your app, check out this list of +[Use Cases](https://webassembly.org/docs/use-cases/). + +It should be noted that using Wasm is not \(yet\) a silver bullet for improving the performance of +a web app. As of right now, using DOM APIs from WebAssembly is still slower than calling them +directly from JavaScript. This is a temporary issue which the [WebAssembly Interface Types](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) proposal aims to +resolve. If you would like to learn more, check out this +[excellent article](https://hacks.mozilla.org/2019/08/webassembly-interface-types/) from Mozilla. + +#### Ok, but why Rust? + +Rust is blazing fast and reliable with its rich type system and ownership model. It has a tough +learning curve but is well worth the effort. Rust has been voted the most loved programming +language in Stack Overflow's Developer Survey five years in a row: +[2016](https://insights.stackoverflow.com/survey/2016#technology-most-loved-dreaded-and-wanted), +[2017](https://insights.stackoverflow.com/survey/2017#most-loved-dreaded-and-wanted), +[2018](https://insights.stackoverflow.com/survey/2018#technology-_-most-loved-dreaded-and-wanted-languages), +[2019](https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages) +and [2020](https://insights.stackoverflow.com/survey/2020#most-loved-dreaded-and-wanted). + +Rust also helps developers write safer code with its rich type system and ownership model. Say +goodbye to hard to track down race condition bugs in JavaScript! In fact, with Rust, most of your +bugs will be caught by the compiler before your app even runs. And don't worry, when your app does +run into an error, you can still get full stack-traces for your Rust code in the browser console. + +#### Alternatives? + +We love to share ideas with other projects and believe we can all help each other reach the full +potential of this exciting new technology. If you're not into Yew, you may like the following +projects: + +* [Percy](https://github.com/chinedufn/percy) - _"A modular toolkit for building isomorphic web +apps with Rust + WebAssembly"_ +* [Seed](https://github.com/seed-rs/seed) - _"A Rust framework for creating web apps"_ + + + diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 00000000000..431b2cb2e76 --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,44 @@ +# Table of contents + +* [Introduction](README.md) + +## Getting Started + +* [Project Setup](getting-started/project-setup/README.md) + * [Using wasm-pack](getting-started/project-setup/using-wasm-pack.md) + * [Using wasm-bindgen](getting-started/project-setup/using-wasm-bindgen.md) + * [Using cargo-web](getting-started/project-setup/using-cargo-web.md) +* [Starter Templates](getting-started/starter-templates.md) +* [Build a Sample App](getting-started/build-a-sample-app.md) +* [Choose web-sys or stdweb](getting-started/choose-web-library.md) +* [Learn through examples](getting-started/examples.md) + +## Core Concepts + +* [Using html!](concepts/html/README.md) + * [Lists](concepts/html/lists.md) + * [Elements](concepts/html/elements.md) + * [Literals & Expressions](concepts/html/literals-and-expressions.md) + * [Components](concepts/html/components.md) +* [Components](concepts/components/README.md) + * [Properties](concepts/components/properties.md) + * [Callbacks](concepts/components/callbacks.md) + * [Refs](concepts/components/refs.md) +* [Agents](concepts/agents.md) +* [Services](concepts/services/README.md) + * [Format](concepts/services/format.md) +* [Router](concepts/router.md) + +## Advanced Topics + +* [Optimisations & Best Practices](advanced-topics/optimizations.md) +* [Low-level library internals](advanced-topics/how-it-works.md) + +## More + +* [CSS](more/css.md) +* [Roadmap](more/roadmap.md) +* [Testing](more/testing.md) +* [Debugging](more/debugging.md) +* [External Libs](more/external-libs.md) + diff --git a/docs/src/advanced-topics/how-it-works.md b/docs/src/advanced-topics/how-it-works.md new file mode 100644 index 00000000000..88d5577c975 --- /dev/null +++ b/docs/src/advanced-topics/how-it-works.md @@ -0,0 +1,8 @@ +--- +description: Low level details about the framework +--- + +# Low-level library internals + +Component-lifecycle state machine, vdom diff algorithm. + diff --git a/docs/src/advanced-topics/optimizations.md b/docs/src/advanced-topics/optimizations.md new file mode 100644 index 00000000000..08d6f5aa3ac --- /dev/null +++ b/docs/src/advanced-topics/optimizations.md @@ -0,0 +1,178 @@ +--- +description: Make your app faster +--- + +# Optimizations & Best Practices + +## neq\_assign + +When a component receives props from its parent component, the `change` method is called. This, in +addition to allowing you to update the component's state, also allows you to return a `ShouldRender` +boolean value that indicates if the component should re-render itself in response to the prop +changes. + +Re-rendering is expensive, and if you can avoid it, you should. As a general rule, you only want to +re-render when the props actually changed. The following block of code represents this rule, +returning `true` if the props differed from the previous props: + +```rust +use yew::ShouldRender; + +#[derive(PartialEq)] +struct ExampleProps; + +struct Example { + props: ExampleProps, +}; + +impl Example { + fn change(&mut self, props: ExampleProps) -> ShouldRender { + if self.props != props { + self.props = props; + true + } else { + false + } + } +} +``` + +But we can go further! This is six lines of boilerplate can be reduced down to one by using a trait +and a blanket implementation for anything that implements `PartialEq`. Check out the `yewtil` +crate's `NeqAssign` trait [here](https://docs.rs/yewtil/*/yewtil/trait.NeqAssign.html). + +## RC + +In an effort to avoid cloning large chunks of data to create props when re-rendering, we can use +smart pointers to only clone the pointer instead. If you use `Rc<_>`s in your props and child +components instead of plain unboxed values, you can delay cloning until you need to modify the data +in the child component, where you use `Rc::make_mut` to clone and get a mutable reference to the +data you want to alter. By not cloning until mutation, child components can reject props identical +to their state-owned props in `Component::change` for almost no performance cost, versus the case +where the data itself needs to be copied into the props struct in the parent before it is compared +and rejected in the child. + +This optimization is most useful for data types that aren't `Copy`. If you can copy your data +easily, then it probably isn't worth putting it behind a smart pointer. For structures that can +contain lots of data like `Vec`, `HashMap`, and `String`, this optimization should be worthwhile. + +This optimization works best if the values are never updated by the children, and even better, if +they are rarely updated by parents. This makes `Rc<_>s` a good choice for wrapping property values +in for pure components. + +## View Functions + +For code readability reasons, it often makes sense to migrate sections of `html!` to their own +functions so you can avoid the rightward drift present in deeply nested HTML. + +## Pure Components/Function Components + +Pure components are components that don't mutate their state, only displaying content and +propagating messages up to normal, mutable components. They differ from view functions in that they +can be used from within the `html!` macro using the component syntax \(``\) +instead of expression syntax \(`{some_view_function()}`\), and that depending on their +implementation, they can be memoized - preventing re-renders for identical props using the +aforementioned `neq_assign` logic. + +Yew doesn't natively support pure or function components, but they are available via external +crates. + +Function components don't exist yet, but in theory, pure components could be generated by using +proc macros and annotating functions. + +## Keyed DOM nodes when they arrive + +## Compile speed optimizations using Cargo Workspaces + +Arguably, the largest drawback to using Yew is the long time it takes to compile. Compile time +seems to correlate with the quantity of code found within `html!` macro blocks. This tends to not +be a significant problem for smaller projects, but for web apps that span multiple pages, it makes +sense to break apart your code across multiple crates to minimize the amount of work the compiler +has to do for each change made. + +You should try to make your main crate handle routing/page selection, move all commonly shared code +to another crate, and then make a different crate for each page, where each page could be a +different component, or just a big function that produces `Html`. In the best case scenario, you go +from rebuilding all of your code on each compile to rebuilding only the main crate, and one of your +page crates. In the worst case, where you edit something in the "common" crate, you will be right +back to where you started: compiling all code that depends on that commonly shared crate, which is +probably everything else. + +If your main crate is too heavyweight, or you want to rapidly iterate on a deeply nested page \(eg. +a page that renders on top of another page\), you can use an example crate to create a simpler +implementation of the main page and render your work-in-progress component on top of that. + +## Build size optimization + +* optimize Rust code + * `wee_aloc` \( using tiny allocator \) + * `cargo.toml` \( defining release profile \) +* optimize wasm code using `wasm-opt` + +More information about code size profiling: [rustwasm book](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size) + +### wee\_alloc + +[wee\_alloc](https://github.com/rustwasm/wee_alloc) is a tiny allocator that is much smaller than +the allocator that is normally used in Rust binaries. Replacing the default allocator with this one +will result in smaller WASM file sizes, at the expense of speed and memory overhead. + +The slower speed and memory overhead are minor in comparison to the size gains made by not including +the default allocator. This smaller file size means that your page will load faster, and so it is +generally recommended that you use this allocator over the default, unless your app is doing some +allocation-heavy work. + +```rust +// Use `wee_alloc` as the global allocator. +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +``` + +### Cargo.toml + +It is possible to setup release build for smaller size using `[profile.release]` section in +`Cargo.toml` + +[Rust profiles documentation](https://doc.rust-lang.org/cargo/reference/profiles.html) + +```text +[profile.release] +# less code to include into binary +panic = 'abort' +# optimization over all codebase ( better optimization, slower build ) +codegen-units = 1 +# optimization for size ( more aggressive ) +opt-level = 'z' +# optimization for size +# opt-level = 's' +# link time optimization using using whole-program analysis +lto = true +``` + +### wasm-opt + +Further more it is possible to optimize size of `wasm` code. + +wasm-opt info: [binaryen project](https://github.com/WebAssembly/binaryen) + +The Rust Wasm book features a section about reducing the size of WASM binaries: +[Shrinking .wasm size](https://rustwasm.github.io/book/game-of-life/code-size.html) + +* using `wasm-pack` which by default optimizes `wasm` code in release builds +* using `wasm-opt` directly on `wasm` files. + +```text +wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm +``` + +#### Build size of 'minimal' example in yew/examples/ + +Note: `wasm-pack` combines optimization for Rust and Wasm code. `wasm-bindgen` is used in this +example without any Rust size optimization. + +| used tool | size | +| :--- | :--- | +| wasm-bindgen | 158KB | +| wasm-binggen + wasm-opt -Os | 116KB | +| wasm-pack | 99 KB | + diff --git a/docs/src/concepts/agents.md b/docs/src/concepts/agents.md new file mode 100644 index 00000000000..c532b16f7dc --- /dev/null +++ b/docs/src/concepts/agents.md @@ -0,0 +1,69 @@ +--- +description: Yew's Actor System +--- + +# Agents + +Agents are similar to Angular's [Services](https://angular.io/guide/architecture-services) +\(but without dependency injection\), and provide a Yew with an +[Actor Model](https://en.wikipedia.org/wiki/Actor_model). Agents can be used to route messages +between components independently of where they sit in the component hierarchy, or they can be used +to create a shared state, and they can also be used to offload computationally expensive tasks from +the main thread which renders the UI. There is also planned support for using agents to allow Yew +applications to communicate across tabs \(in the future\). + +In order for agents to run concurrently, Yew uses +[web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers). + +## Lifecycle + +![Agent lifecycle](https://user-images.githubusercontent.com/42674621/79125224-b6481d80-7d95-11ea-8e6a-ab9b52d1d8ac.png) + +## Types of Agents + +#### Reaches + +* Context - There will exist at most one instance of a Context Agent at any given time. Bridges will + + spawn or connect to an already spawned agent on the UI thread. This can be used to coordinate state + + between components or other agents. When no bridges are connected to this agent, the agent will + + disappear. + +* Job - Spawn a new agent on the UI thread for every new bridge. This is good for moving shared but + + independent behavior that communicates with the browser out of components. \(TODO verify\) When the + + task is done, the agent will disappear. + +* Public - Same as Context, but runs on its own web worker. +* Private - Same as Job, but runs on its own web worker. +* Global \(WIP\) + +## Communication between Agents and Components + +### Bridges + +A bridge allows bi-directional communication between an agent and a component. Bridges also allow +agents to communicate with one another. + +### Dispatchers + +A dispatcher allows uni-directional communication between a component and an agent. A dispatcher +allows a component to send messages to an agent. + +## Overhead + +Agents that live in their own separate web worker \(Private and Public\) incur serialization +overhead on the messages they send and receive. They use [bincode](https://github.com/servo/bincode) +to communicate with other threads, so the cost is substantially higher than just calling a function. +Unless the cost of computation will outweigh the cost of message passing, you should contain your +logic in the UI thread agents \(Job or Context\). + +## Further reading + +* The [pub\_sub](https://github.com/yewstack/yew/tree/master/examples/pub_sub) example shows how + + components can use agents to communicate with each other. + diff --git a/docs/src/concepts/components/README.md b/docs/src/concepts/components/README.md new file mode 100644 index 00000000000..9e89e38db13 --- /dev/null +++ b/docs/src/concepts/components/README.md @@ -0,0 +1,205 @@ +--- +description: Components and their lifecycle hooks +--- + +# Components + +## What are Components? + +Components are the building blocks of Yew. They manage their own state and can render themselves to +the DOM. Components are created by implementing the `Component` trait which describes the lifecycle +of a component. + +## Lifecycle + +{% hint style="info" %} +`Contribute to our docs:` [Add a diagram of the component lifecycle](https://github.com/yewstack/docs/issues/22) +{% endhint %} + +## Lifecycle Methods + +### Create + +When a component is created, it receives properties from its parent component as well as a +`ComponentLink`. The properties can be used to initialize the component's state and the "link" can +be used to register callbacks or send messages to the component. + +It is common to store the props and the link in your component struct, like so: + +```rust +pub struct MyComponent { + props: Props, + link: ComponentLink, +} + +impl Component for MyComponent { + type Properties = Props; + // ... + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + MyComponent { props, link } + } + + // ... +} +``` + +### View + +Components declare their layout in the `view()` method. Yew provides the `html!` macro for declaring +HTML and SVG nodes and their listeners as well as child components. The macro acts a lot like +React's JSX, but uses Rust expressions instead of JavaScript. + +```rust +impl Component for MyComponent { + // ... + + fn view(&self) -> Html { + let onclick = self.link.callback(|_| Msg::Click); + html! { + + } + } +} +``` + +For usage details, check out the `html!` guide: + +{% page-ref page="../html/" %} + +### Rendered + +The `rendered()` component lifecycle method is called after `view()` is processed and Yew has +rendered your component, but before the browser refreshes the page. A component may wish to +implement this method to perform actions that can only be done after the component has rendered +elements. You can check whether this is the first time the component was rendered via the +`first_render` parameter. + +```rust +use stdweb::web::html_element::InputElement; +use stdweb::web::IHtmlElement; +use yew::prelude::*; + +pub struct MyComponent { + node_ref: NodeRef, +} + +impl Component for MyComponent { + // ... + + fn view(&self) -> Html { + html! { + + } + } + + fn rendered(&mut self, first_render: bool) { + if first_render { + if let Some(input) = self.node_ref.try_into::() { + input.focus(); + } + } + } +} +``` + +{% hint style="info" %} +Note that this lifecycle method does not require an implementation and will do nothing by default +{% endhint %} + +### Update + +Components are dynamic and can register to receive asynchronous messages. The `update()` lifecycle +method is called for each message. This allows the component to update itself based on what the +message was, and determine if it needs to re-render itself. Messages can be triggered by HTML +elements listeners or be sent by child components, Agents, Services, or Futures. + +Here's an example of what `update()` could look like: + +```rust +pub enum Msg { + SetInputEnabled(bool) +} + +impl Component for MyComponent { + type Message = Msg; + + // ... + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::SetInputEnabled(enabled) => { + if self.input_enabled != enabled { + self.input_enabled = enabled; + true // Re-render + } else { + false + } + } + } + } +} +``` + +### Change + +Components may be re-rendered by their parents. When this happens, they could receive new properties +and choose to re-render. This design facilitates parent to child component communication through +changed properties. + +A typical implementation would look like: + +```rust +impl Component for MyComponent { + // ... + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + if self.props != props { + self.props = props; + true + } else { + false + } + } +} +``` + +### Destroy + +After Components are unmounted from the DOM, Yew calls the `destroy()` lifecycle method to support +any necessary clean up operations. This method is optional and does nothing by default. + +## Associated Types + +The `Component` trait has two associated types: `Message` and `Properties`. + +```rust +impl Component for MyComponent { + type Message = Msg; + type Properties = Props; + + // ... +} +``` + +`Message` represents a variety of messages that can be processed by the component to trigger some +side effect. For example, you may have a `Click` message which triggers an API request or toggles +the appearance of a UI component. It is common practice to create an enum called `Msg` in your +component's module and use that as the message type in the component. It is common to shorten +"message" to "msg". + +```rust +enum Msg { + Click, +} +``` + +`Properties` represents the information passed to a component from its parent. This type must +implements the `Properties` trait \(usually by deriving it\) and can specify whether certain +properties are required or optional. This type is used when creating and updating a component. It +is common practice to create a struct called `Props` in your component's module and use that as the +component's `Properties` type. It is common to shorten "properties" to "props". Since props are +handed down from parent components, the root component of your application typically has a +`Properties` type of `()`. If you wish to specify properties for your root component, use the +`App::mount_with_props` method. + diff --git a/docs/src/concepts/components/callbacks.md b/docs/src/concepts/components/callbacks.md new file mode 100644 index 00000000000..e828dc96d91 --- /dev/null +++ b/docs/src/concepts/components/callbacks.md @@ -0,0 +1,41 @@ +--- +description: ComponentLink and Callbacks +--- + +# Callbacks + +The component "link" is the mechanism through which components are able to register callbacks and +update themselves. + +## ComponentLink API + +### callback + +Registers a callback that will send a message to the component's update mechanism when it is +executed. Under the hood, it will call `send_self` with the message that is returned by the +provided closure. A `Fn(IN) -> Vec` is provided and a `Callback` is returned. + +### send\_message + +Sends a message to the component immediately after the current loop finishes, causing another +update loop to initiate. + +### send\_message\_batch + +Registers a callback that sends a batch of many messages at once when it is executed. If any of the +messages cause the component to re-render, the component will re-render after all messages in the +batch have been processed. A `Fn(IN) -> COMP::Message` is provided and a `Callback` is returned. + +## Callbacks + +_\(This might need its own short page.\)_ + +Callbacks are used to communicate with services, agents, and parent components within Yew. They are +just a `Fn` wrapped by an `Rc` to allow them to be cloned. + +They have an `emit` function that takes their `` type as an argument and converts that to a +message expected by its destination. If a callback from a parent is provided in props to a child +component, the child can call `emit` on the callback in its `update` lifecycle hook to send a +message back to its parent. Closures or Functions provided as props inside the `html!` macro are +automatically converted to Callbacks. + diff --git a/docs/src/concepts/components/properties.md b/docs/src/concepts/components/properties.md new file mode 100644 index 00000000000..6c0f49c8ca3 --- /dev/null +++ b/docs/src/concepts/components/properties.md @@ -0,0 +1,95 @@ +--- +description: Parent to child communication +--- + +# Properties + +Properties enable child and parent components to communicate with each other. + +## Derive macro + +Don't try to implement `Properties` yourself, derive it by using `#[derive(Properties)]` instead. + +{% hint style="info" %} +Types for which you derive `Properties` must also implement `Clone`. This can be done by either +using `#[derive(Properties, Clone)` or manually implementing `Clone` for your type. +{% endhint %} + +### Required attributes + +The fields within a struct that derives `Properties` are required by default. When a field is +missing and the component is created in the `html!` macro, a compiler error is returned. For fields +with optional properties, use the `#[prop_or_default]` attribute to use the default value for that +type when the prop is not specified. To specify a value, use the `#[prop_or(value)]` attribute where +value is the default value for the property or alternatively use `#[prop_or_else(function)]` where +`function` returns the default value. For example, to default a boolean value as `true`, use the +attribute `#[prop_or(true)]`. It is common for optional properties to use the `Option` enum which +has the default value `None`. + +### PartialEq + +It is likely to make sense to derive `PartialEq` on your props if you can do this. Using `PartialEq` +makes it much easier to avoid unnecessary rerendering \(this is explained in the **Optimizations & +Best Practices** section\). + +## Memory/speed overhead of using Properties + +In `Component::view`, you take a reference to the component's state, and use that to create `Html`. +Properties, however, are owned values. This means that in order to create them and pass them to +child components, we need to take ownership of the references provided in the `view` function. This +is done by implicitly cloning the references as they are passed to components in order to get owned +values. + +This means that each component has its own distinct copy of the state passed down from its parent, +and that whenever you re-render a component, the props for all child components of the re-rendering +component will have to be cloned. + +The implication of this is if you would otherwise be passing _huge_ amounts of data down as props +\(Strings that are 10s of kilobytes in size\), you may want to consider turning your child component +into a function which returns `Html` that the parent calls, as this means that data does not have to +be cloned. + +If you won't need to modify the data passed down through props you can wrap it in an `Rc` so that +only a reference-counted pointer to the data is cloned, instead of the actual data itself. + +## Example + +```rust +use std::rc::Rc; +use yew::Properties; + +#[derive(Clone, PartialEq)] +pub enum LinkColor { + Blue, + Red, + Green, + Black, + Purple, +} + +impl Default for LinkColor { + fn default() -> Self { + // The link color will be blue unless otherwise specified. + LinkColor::Blue + } +} + +#[derive(Properties, Clone, PartialEq)] +pub struct LinkProps { + /// The link must have a target. + href: String, + /// If the link text is huge, this will make copying the string much cheaper. + /// This isn't usually recommended unless performance is known to be a problem. + text: Rc, + /// Color of the link. + #[prop_or_default] + color: LinkColor, + /// The view function will not specify a size if this is None. + #[prop_or_default] + size: Option, + /// When the view function doesn't specify active, it defaults to true. + #[prop_or(true)] + active: bool, +} +``` + diff --git a/docs/src/concepts/components/refs.md b/docs/src/concepts/components/refs.md new file mode 100644 index 00000000000..361da95681d --- /dev/null +++ b/docs/src/concepts/components/refs.md @@ -0,0 +1,29 @@ +--- +description: Out-of-band DOM access +--- + +# Refs + +## Refs + +The `ref` keyword can be used inside of any HTML element or component to get the DOM `Element` that +the item is attached to. This can be used to make changes to the DOM outside of the `view` lifecycle +method. + +This is useful for getting ahold of canvas elements, or scrolling to different sections of a page. + +The syntax is: + +```rust +// In create +self.node_ref = NodeRef::default(); + +// In view +html! { +
+} + +// In update +let has_attributes = self.node_ref.try_into::().has_attributes(); +``` + diff --git a/docs/src/concepts/html/README.md b/docs/src/concepts/html/README.md new file mode 100644 index 00000000000..612fa8b2403 --- /dev/null +++ b/docs/src/concepts/html/README.md @@ -0,0 +1,37 @@ +--- +description: The procedural macro for generating HTML and SVG +--- + +# Using html! + +The `html!` macro allows you to write HTML and SVG code declaratively. It is similar to JSX +\(a Javascript extension which allows you to write HTML-like code inside of Javascript\). + +**Important notes** + +1. The `html!` macro only accepts one root html node \(you can counteract this by + + [using fragments or iterators](lists.md)\) + +2. An empty `html! {}` invocation is valid and will not render anything +3. Literals must always be quoted and wrapped in braces: `html! { "Hello, World" }` + +{% hint style="info" %} +The `html!` macro can reach easily the default recursion limit of the compiler. It is advised to +bump its value if you encounter compilation errors. Use an attribute like +`#![recursion_limit="1024"]` in the crate root \(i.e. either `lib.rs` or `main.rs`\) to overcome +the problem. See the +[official documentation](https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute) +and +[this Stack Overflow question](https://stackoverflow.com/questions/27454761/what-is-a-crate-attribute-and-where-do-i-add-it) +for details. +{% endhint %} + +{% page-ref page="lists.md" %} + +{% page-ref page="elements.md" %} + +{% page-ref page="literals-and-expressions.md" %} + +{% page-ref page="components.md" %} + diff --git a/docs/src/concepts/html/components.md b/docs/src/concepts/html/components.md new file mode 100644 index 00000000000..5a456bd5909 --- /dev/null +++ b/docs/src/concepts/html/components.md @@ -0,0 +1,115 @@ +--- +description: Create complex layouts with component hierarchies +--- + +# Components + +## Basic + +Any type that implements `Component` can be used in the `html!` macro: + +```rust +html!{ + <> + // No properties + + + // With Properties + + + // With the whole set of props provided at once + + +} +``` + +## Nested + +Components can be passed children if they have a `children` field in their `Properties`. + +{% code title="parent.rs" %} +```rust +html! { + +

{ "Hi" }

+
{ "Hello" }
+
+} +``` +{% endcode %} + +{% code title="container.rs" %} +```rust +pub struct Container(Props); + +#[derive(Properties, Clone)] +pub struct Props { + pub children: Children, +} + +impl Component for Container { + type Properties = Props; + + // ... + + fn view(&self) -> Html { + html! { +
+ { self.0.children.render() } +
+ } + } +} +``` +{% endcode %} + +{% hint style="info" %} +Types for which you derive `Properties` must also implement `Clone`. This can be done by either +using `#[derive(Properties, Clone)]` or manually implementing `Clone` for your type. +{% endhint %} + +## Nested Children with Props + +Nested component properties can be accessed and mutated if the containing component types its +children. In the following example, the `List` component can wrap `ListItem` components. For a +real world example of this pattern, check out the `yew-router` source code. For a more advanced +example, check out the `nested-list` example in the main yew repository. + +{% code title="parent.rs" %} +```rust +html! { + + + + + +} +``` +{% endcode %} + +{% code title="list.rs" %} +```rust +pub struct List(Props); + +#[derive(Properties, Clone)] +pub struct Props { + pub children: ChildrenWithProps, +} + +impl Component for List { + type Properties = Props; + + // ... + + fn view(&self) -> Html { + html!{{ + for self.0.children.iter().map(|mut item| { + item.props.value = format!("item-{}", item.props.value); + item + }) + }} + } +} +``` +{% endcode %} + diff --git a/docs/src/concepts/html/elements.md b/docs/src/concepts/html/elements.md new file mode 100644 index 00000000000..07396d8ed30 --- /dev/null +++ b/docs/src/concepts/html/elements.md @@ -0,0 +1,262 @@ +--- +description: Both HTML and SVG elements are supported +--- + +# Elements + +## Tag Structure + +Element tags must either self-close `<... />` or have a corresponding close tag for each open tag + +{% tabs %} +{% tab title="Open - Close" %} +```rust +html! { +
+} +``` +{% endtab %} + +{% tab title="INVALID" %} +```rust +html! { +
// <- MISSING CLOSE TAG +} +``` +{% endtab %} + +{% tab title="Self-Closing" %} +```rust +html! { + +} +``` +{% endtab %} + +{% tab title="INVALID" %} +```rust +html! { + // <- MISSING SELF-CLOSE +} +``` +{% endtab %} +{% endtabs %} + +{% hint style="info" %} +For convenience, elements which _usually_ require a closing tag are **allowed** to self-close. For example, writing `html! {
}` is valid. +{% endhint %} + +## Children + +Create complex nested HTML and SVG layouts with ease: + +{% tabs %} +{% tab title="HTML" %} +```rust +html! { +
+
+
+ + + + +