<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Journey to Code — Practical web development, real projects, and lessons from the build.]]></title><description><![CDATA[Follow my journey building real-world software with React, Node.js, Rust, APIs, testing, and modern web technologies. Projects, lessons learned, and practical development articles.]]></description><link>https://journeytocode.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/65ece53114d8768ce885db88/12bc582d-0f50-4577-a036-6589de67ca83.png</url><title>Journey to Code — Practical web development, real projects, and lessons from the build.</title><link>https://journeytocode.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 01 Nov 2026 08:12:44 GMT</lastBuildDate><atom:link href="https://journeytocode.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[JavaScript Does Not Get the Rules.]]></title><description><![CDATA[Compile a Rust idle-game engine to WebAssembly, give JavaScript a small adapter instead of the rules, and run the same tested game in a browser.]]></description><link>https://journeytocode.hashnode.dev/rust-game-webassembly-wasm-bindgen</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/rust-game-webassembly-wasm-bindgen</guid><category><![CDATA[Rust]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Fri, 30 Oct 2026 15:15:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/0859a869-adcb-4eca-9ac0-ab79d08e0737.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For ten articles, the only thing that has ever run our game is <code>main()</code>, in Rust, in a terminal.</p>
<p>Today, a browser runs it.</p>
<p>Not a rewrite. Not a port. The same library, with the same twenty-eight tests, compiled for a different machine. The browser gets a handful of operations and a save that is already text. It does not get the rules.</p>
<p>By the end, a web page shows this:</p>
<pre><code class="language-text">One hour away.
Stage 1 -&gt; 128  Level 1 -&gt; 47  Gold 0 -&gt; 16637
</code></pre>
<p>Stage 128 and level 47 are the numbers <code>tests/idle_hour.rs</code> has been checking since the ninth article. Same engine. Same hour. The call came from JavaScript.</p>
<p>Along the way we meet WebAssembly, two tools that do the glue work, and one new rule about what is allowed to cross a border.</p>
<hr />
<h2>Our starting point</h2>
<p>This series builds a small idle game: a player clicks to fight slimes, earns gold and XP, and keeps progressing while you are away.</p>
<p>If you are only reading along, skip past this code to "A second machine."</p>
<p>If you are building along, you need the library as it stood at the end of the last article: a project called <code>rustbound-idle</code> with seven source files and two outside tests. (If you kept your project, you already have all of this. The <code>article_10</code> branch of the companion repository has it too.)</p>
<pre><code class="language-text">rustbound-idle/
├── Cargo.toml
├── Cargo.lock
├── src/
│   ├── lib.rs
│   ├── main.rs
│   ├── rules.rs
│   ├── enemy.rs
│   ├── player.rs
│   ├── game.rs
│   └── save.rs
└── tests/
    ├── idle_hour.rs
    └── save_and_restore.rs
</code></pre>
<p><code>Cargo.toml</code>:</p>
<p><strong>Cargo.toml</strong></p>
<pre><code class="language-toml">[package]
name = "rustbound-idle"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
</code></pre>
<details>
<summary><code>src/lib.rs</code> · 10 lines</summary>
<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;
mod save;

<p>pub use enemy::Enemy;
pub use game::Game;
pub use player::{Player, PurchaseOutcome};
pub use save::LoadError;
</p></code></pre><p></p>
</details>

<details>
<summary><code>src/main.rs</code> · 71 lines</summary>
<pre><code class="language-rust">use rustbound_idle::{Game, PurchaseOutcome};

<p>fn main() {
    let mut game = Game::new();</p>
<pre><code>while game.stage &lt;= 3 {
    let stage = game.stage;
    let level = game.player.level;

    game.click();

    if game.stage &gt; stage {
        println!(
            "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
            game.player.gold,
            game.player.xp,
            game.player.xp_needed()
        );

        if game.player.level &gt; level {
            println!(
                "Level up! Now level {}. Click damage: {}",
                game.player.level,
                game.player.click_damage()
            );
        }

        println!(
            "  Next up: {} with {} health.",
            game.enemy.name, game.enemy.health
        );
    }

    match game.buy_click_upgrade() {
        PurchaseOutcome::Purchased =&gt; println!(
            "Bought click upgrade {}! Click damage: {}  Gold left: {}",
            game.player.click_upgrade_level,
            game.player.click_damage(),
            game.player.gold
        ),
        PurchaseOutcome::NotEnoughGold =&gt; {}
    }
}

let save = game.save_json();
println!("Saved: {save}");

drop(game);

let mut game = match Game::from_saved_json(&amp;save) {
    Ok(game) =&gt; game,
    Err(error) =&gt; {
        println!("Could not load the save: {error:?}");
        return;
    }
};

println!("Loaded. Stage {}, level {}.", game.stage, game.player.level);

let stage = game.stage;
let level = game.player.level;
let gold = game.player.gold;

game.tick(3600);

println!("One hour away.");
println!(
    "Stage {} -&gt; {}  Level {} -&gt; {}  Gold {} -&gt; {}",
    stage, game.stage, level, game.player.level, gold, game.player.gold
);
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<details>
<summary><code>src/rules.rs</code> · 105 lines</summary>
<pre><code class="language-rust">pub(crate) fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

<p>pub(crate) fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}</p>
<p>pub(crate) fn is_defeated(health: u32) -&gt; bool {
    health == 0
}</p>
<p>pub(crate) fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}</p>
<p>pub(crate) fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}</p>
<p>pub(crate) fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}</p>
<p>pub(crate) fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}</p>
<p>pub(crate) fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}</p>
<p>pub(crate) fn idle_damage_for_level(level: u32) -&gt; u32 {
    level
}</p>
<p>pub(crate) fn click_upgrade_bonus(upgrades: u32) -&gt; u32 {
    upgrades.saturating_mul(2)
}</p>
<p>pub(crate) fn click_upgrade_cost(upgrades: u32) -&gt; u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}</p>
<p>#[cfg(test)]
mod tests {
    use super::*;</p>
<pre><code>#[test]
fn click_damage_adds_base_and_bonus() {
    assert_eq!(click_damage(3, 4), 7);
}

#[test]
fn health_never_drops_below_zero() {
    assert_eq!(health_after_hit(3, 10), 0);
}

#[test]
fn zero_health_is_defeated() {
    assert!(is_defeated(0));
    assert!(!is_defeated(1));
}

#[test]
fn later_stages_have_tougher_enemies() {
    assert_eq!(enemy_health_for_stage(1), 20);
    assert_eq!(enemy_health_for_stage(2), 30);
}

#[test]
fn later_stages_pay_better() {
    assert_eq!(gold_reward_for_stage(1), 5);
    assert_eq!(gold_reward_for_stage(2), 7);
    assert_eq!(xp_reward_for_stage(1), 6);
    assert_eq!(xp_reward_for_stage(2), 8);
}

#[test]
fn levels_need_more_xp_as_they_go() {
    assert_eq!(xp_to_next_level(1), 20);
    assert_eq!(xp_to_next_level(2), 35);
}

#[test]
fn levels_make_the_player_hit_harder() {
    assert_eq!(base_damage_for_level(1), 5);
    assert_eq!(base_damage_for_level(2), 6);
}

#[test]
fn idle_damage_grows_with_level() {
    assert_eq!(idle_damage_for_level(1), 1);
    assert_eq!(idle_damage_for_level(2), 2);
}

#[test]
fn upgrades_cost_more_each_time() {
    assert_eq!(click_upgrade_cost(0), 10);
    assert_eq!(click_upgrade_cost(1), 20);
}
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<details>
<summary><code>src/enemy.rs</code> · 19 lines</summary>
<pre><code class="language-rust">use crate::rules::{enemy_health_for_stage, gold_reward_for_stage, xp_reward_for_stage};

<p>pub struct Enemy {
    pub name: String,
    pub health: u32,
    pub(crate) gold_reward: u32,
    pub(crate) xp_reward: u32,
}</p>
<p>impl Enemy {
    pub(crate) fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}
</p></code></pre><p></p>
</details>

<details>
<summary><code>src/player.rs</code> · 114 lines</summary>
<pre><code class="language-rust">use crate::rules::{
    base_damage_for_level, click_damage, click_upgrade_bonus, click_upgrade_cost, xp_to_next_level,
};

<p>pub struct Player {
    pub gold: u32,
    pub xp: u32,
    pub level: u32,
    pub click_upgrade_level: u32,
}</p>
<p>#[derive(Debug, PartialEq)]
pub enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}</p>
<p>impl Player {
    pub(crate) fn new() -&gt; Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }</p>
<pre><code>pub(crate) fn add_gold(&amp;mut self, amount: u32) {
    self.gold = self.gold.saturating_add(amount);
}

pub(crate) fn add_xp(&amp;mut self, amount: u32) {
    self.xp = self.xp.saturating_add(amount);

    while self.xp &gt;= self.xp_needed() {
        self.xp -= self.xp_needed();
        self.level = self.level.saturating_add(1);
    }
}

pub fn xp_needed(&amp;self) -&gt; u32 {
    xp_to_next_level(self.level)
}

pub fn click_damage(&amp;self) -&gt; u32 {
    click_damage(self.base_damage(), self.bonus_damage())
}

fn base_damage(&amp;self) -&gt; u32 {
    base_damage_for_level(self.level)
}

fn bonus_damage(&amp;self) -&gt; u32 {
    click_upgrade_bonus(self.click_upgrade_level)
}

pub(crate) fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
    let cost = click_upgrade_cost(self.click_upgrade_level);

    if self.gold &lt; cost {
        return PurchaseOutcome::NotEnoughGold;
    }

    self.gold -= cost;
    self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

    PurchaseOutcome::Purchased
}
</code></pre>
<p>}</p>
<p>#[cfg(test)]
mod tests {
    use super::*;</p>
<pre><code>#[test]
fn leveling_up_keeps_leftover_xp() {
    let mut player = Player::new();

    player.add_xp(25);

    assert_eq!(player.level, 2);
    assert_eq!(player.xp, 5);
}

#[test]
fn a_big_reward_can_skip_levels() {
    let mut player = Player::new();

    player.add_xp(60);

    assert_eq!(player.level, 3);
    assert_eq!(player.xp, 5);
}

#[test]
fn buying_an_upgrade_needs_gold() {
    let mut player = Player::new();

    assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::NotEnoughGold);
    assert_eq!(player.click_upgrade_level, 0);
    assert_eq!(player.bonus_damage(), 0);
}

#[test]
fn buying_an_upgrade_spends_gold() {
    let mut player = Player::new();
    player.gold = 12;

    assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased);
    assert_eq!(player.gold, 2);
    assert_eq!(player.click_upgrade_level, 1);
    assert_eq!(player.bonus_damage(), 2);
}
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<details>
<summary><code>src/game.rs</code> · 158 lines</summary>
<pre><code class="language-rust">use crate::enemy::Enemy;
use crate::player::{Player, PurchaseOutcome};
use crate::rules::{health_after_hit, idle_damage_for_level, is_defeated};

<p>pub struct Game {
    pub stage: u32,
    pub player: Player,
    pub enemy: Enemy,
}</p>
<p>impl Game {
    pub fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }</p>
<pre><code>pub fn click(&amp;mut self) {
    attack(&amp;self.player, &amp;mut self.enemy);
    self.resolve_hit();
}

pub fn tick(&amp;mut self, seconds: u32) {
    for _ in 0..seconds {
        let damage = idle_damage_for_level(self.player.level);

        self.enemy.health = health_after_hit(self.enemy.health, damage);
        self.resolve_hit();
    }
}

fn resolve_hit(&amp;mut self) {
    if is_defeated(self.enemy.health) {
        self.defeat_enemy();
    }
}

fn defeat_enemy(&amp;mut self) {
    self.player.add_gold(self.enemy.gold_reward);
    self.player.add_xp(self.enemy.xp_reward);

    self.stage = self.stage.saturating_add(1);
    self.enemy = Enemy::for_stage(self.stage);
}

pub fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
    self.player.buy_click_upgrade()
}
</code></pre>
<p>}</p>
<p>impl Default for Game {
    fn default() -&gt; Game {
        Game::new()
    }
}</p>
<p>fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    enemy.health = health_after_hit(enemy.health, player.click_damage());
}</p>
<p>#[cfg(test)]
mod tests {
    use super::*;</p>
<pre><code>#[test]
fn attack_reduces_enemy_health() {
    let player = Player::new();

    let mut enemy = Enemy::for_stage(1);
    enemy.health = 10;

    attack(&amp;player, &amp;mut enemy);

    assert_eq!(enemy.health, 5);
}

#[test]
fn a_new_game_starts_at_stage_one() {
    let game = Game::new();

    assert_eq!(game.stage, 1);
    assert_eq!(game.enemy.health, 20);
    assert_eq!(game.player.gold, 0);
}

#[test]
fn clicking_damages_the_enemy() {
    let mut game = Game::new();

    game.click();

    assert_eq!(game.enemy.health, 15);
}

#[test]
fn defeating_an_enemy_pays_the_player() {
    let mut game = Game::new();
    game.enemy.health = 1;

    game.click();

    assert_eq!(game.stage, 2);
    assert_eq!(game.enemy.health, 30);
    assert_eq!(game.player.gold, 5);
    assert_eq!(game.player.xp, 6);
}

#[test]
fn one_second_is_one_idle_hit() {
    let mut game = Game::new();

    game.tick(1);

    assert_eq!(game.enemy.health, 19);
}

#[test]
fn zero_seconds_changes_nothing() {
    let mut game = Game::new();

    game.tick(0);

    assert_eq!(game.stage, 1);
    assert_eq!(game.enemy.health, 20);
}

#[test]
fn enough_time_defeats_the_enemy() {
    let mut game = Game::new();

    game.tick(20);

    assert_eq!(game.stage, 2);
    assert_eq!(game.enemy.health, 30);
    assert_eq!(game.player.gold, 5);
    assert_eq!(game.player.xp, 6);
}

#[test]
fn one_long_tick_matches_many_short_ones() {
    let mut long = Game::new();
    let mut short = Game::new();

    long.tick(45);

    for _ in 0..45 {
        short.tick(1);
    }

    assert_eq!(long.stage, short.stage);
    assert_eq!(long.enemy.health, short.enemy.health);
    assert_eq!(long.player.gold, short.player.gold);
    assert_eq!(long.player.xp, short.player.xp);
    assert_eq!(long.player.level, short.player.level);
}
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<details>
<summary><code>src/save.rs</code> · 130 lines</summary>
<pre><code class="language-rust">use crate::enemy::Enemy;
use crate::game::Game;
use serde::{Deserialize, Serialize};

<p>const SAVE_VERSION: u32 = 1;</p>
<p>#[derive(Debug, PartialEq)]
pub enum LoadError {
    Unreadable,
    UnsupportedVersion(u32),
}</p>
<p>#[derive(Serialize, Deserialize)]
struct PlayerSave {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}</p>
<p>#[derive(Serialize, Deserialize)]
struct SaveData {
    save_version: u32,
    stage: u32,
    player: PlayerSave,
    enemy_health: u32,
}</p>
<p>impl Game {
    pub fn save_json(&amp;self) -&gt; String {
        let save = SaveData {
            save_version: SAVE_VERSION,
            stage: self.stage,
            player: PlayerSave {
                gold: self.player.gold,
                xp: self.player.xp,
                level: self.player.level,
                click_upgrade_level: self.player.click_upgrade_level,
            },
            enemy_health: self.enemy.health,
        };</p>
<pre><code>    serde_json::to_string(&amp;save).expect("a save made of numbers should always serialize")
}

pub fn from_saved_json(json: &amp;str) -&gt; Result&lt;Game, LoadError&gt; {
    let save: SaveData = match serde_json::from_str(json) {
        Ok(save) =&gt; save,
        Err(_) =&gt; return Err(LoadError::Unreadable),
    };

    if save.save_version != SAVE_VERSION {
        return Err(LoadError::UnsupportedVersion(save.save_version));
    }

    let mut game = Game::new();

    game.stage = save.stage;
    game.player.gold = save.player.gold;
    game.player.xp = save.player.xp;
    game.player.level = save.player.level;
    game.player.click_upgrade_level = save.player.click_upgrade_level;

    game.enemy = Enemy::for_stage(save.stage);
    game.enemy.health = save.enemy_health;

    Ok(game)
}
</code></pre>
<p>}</p>
<p>#[cfg(test)]
mod tests {
    use super::*;</p>
<pre><code>#[test]
fn a_new_game_saves_its_starting_numbers() {
    let json = Game::new().save_json();

    assert_eq!(
        json,
        r#"{"save_version":1,"stage":1,"player":{"gold":0,"xp":0,"level":1,"click_upgrade_level":0},"enemy_health":20}"#
    );
}

#[test]
fn a_loaded_game_rebuilds_the_enemy_from_the_rules() {
    let mut game = Game::new();
    game.tick(600);

    let restored = Game::from_saved_json(&amp;game.save_json()).expect("the save should load");

    assert_eq!(restored.stage, game.stage);
    assert_eq!(restored.enemy.health, game.enemy.health);
    assert_eq!(restored.enemy.gold_reward, game.enemy.gold_reward);
    assert_eq!(restored.enemy.xp_reward, game.enemy.xp_reward);
}

#[test]
fn nonsense_is_not_a_save() {
    assert!(matches!(
        Game::from_saved_json("definitely not a save"),
        Err(LoadError::Unreadable)
    ));
}

#[test]
fn a_save_with_missing_pieces_is_not_a_save() {
    let json = r#"{"save_version":1,"stage":4}"#;

    assert!(matches!(
        Game::from_saved_json(json),
        Err(LoadError::Unreadable)
    ));
}

#[test]
fn a_save_from_another_version_is_refused() {
    let json = r#"{
        "save_version": 99,
        "stage": 1,
        "player": { "gold": 0, "xp": 0, "level": 1, "click_upgrade_level": 0 },
        "enemy_health": 20
    }"#;

    assert!(matches!(
        Game::from_saved_json(json),
        Err(LoadError::UnsupportedVersion(99))
    ));
}
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<details>
<summary><code>tests/idle_hour.rs</code> · 11 lines</summary>
<pre><code class="language-rust">use rustbound_idle::Game;

<p>#[test]
fn an_hour_away_moves_the_game_forward() {
    let mut game = Game::new();</p>
<pre><code>game.tick(3600);

assert_eq!(game.stage, 128);
assert_eq!(game.player.level, 47);
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<details>
<summary><code>tests/save_and_restore.rs</code> · 18 lines</summary>
<pre><code class="language-rust">use rustbound_idle::Game;

<p>#[test]
fn a_restored_game_plays_the_same_hour() {
    let mut original = Game::new();
    original.tick(600);</p>
<pre><code>let mut restored = Game::from_saved_json(&amp;original.save_json()).expect("the save should load");

original.tick(3600);
restored.tick(3600);

assert_eq!(restored.stage, original.stage);
assert_eq!(restored.enemy.health, original.enemy.health);
assert_eq!(restored.player.gold, original.player.gold);
assert_eq!(restored.player.xp, original.player.xp);
assert_eq!(restored.player.level, original.player.level);
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<p>Run <code>cargo test</code>, and twenty-eight tests pass. Run <code>cargo run</code>, and the player clears three stages, saves, loads, and spends an hour away.</p>
<hr />
<h2>A second machine</h2>
<p>Everything we have compiled so far has been for the computer in front of you. <code>cargo build</code> asks Rust to produce a program for <em>this</em> machine: this processor, this operating system.</p>
<p>Rust can produce programs for other machines too. A phone. A server with a different processor. Each one is called a <strong>target</strong>, and you pick it when you build.</p>
<p>WebAssembly is one of those targets.</p>
<p>It is not a real machine. It is a description of one, agreed on by the people who make browsers, so that every browser can run the same compiled code. Compile Rust for it, and the result is a file a browser can load and run, the way it loads and runs JavaScript.</p>
<p>The target has a long name:</p>
<pre><code class="language-text">wasm32-unknown-unknown
</code></pre>
<p>The three parts are the processor, the vendor, and the operating system. The processor is 32-bit WebAssembly. The other two are <em>unknown</em>, and they mean it.</p>
<p>There is no operating system. No files, no clock, no terminal, no <code>println!</code>. A program compiled for this target can do arithmetic and talk to whoever loaded it, and that is all.</p>
<p>That should sound like a problem.</p>
<p>It is not, and the reason is the wall we built two articles ago. Our library has never opened a file, never asked the time, never printed. It takes numbers and text, and it gives back numbers and text. <code>main.rs</code> does the printing, and <code>main.rs</code> is not coming with us.</p>
<p>Install the target:</p>
<pre><code class="language-bash">rustup target add wasm32-unknown-unknown
</code></pre>
<p>That downloads Rust's standard library, compiled for WebAssembly, so that <code>String</code> and <code>Vec</code> and <code>saturating_add</code> all exist on the other side. It takes a moment, and it only happens once.</p>
<p><strong>Question:</strong> Why does <code>main.rs</code> stay behind when we compile the library for the browser?</p>
<ol>
<li>WebAssembly can only compile files named <code>lib.rs</code></li>
<li><code>main.rs</code> prints to a terminal, and this browser target has no terminal</li>
<li>JavaScript automatically replaces every Rust <code>main()</code> function</li>
</ol>
<p><strong>Answer:</strong> 2. <code>main.rs</code> prints to a terminal, and this browser target has no terminal</p>
<blockquote>
<p><strong>Explanation:</strong> The engine only calculates with numbers and text. The terminal-specific printing stays in <code>main.rs</code> on the machine that has a terminal.</p>
</blockquote>
<hr />
<h2>Two tools</h2>
<p>WebAssembly has one more limitation that matters to us.</p>
<p>At the raw WebAssembly boundary we are using today, values cross as WebAssembly's small built-in types. A Rust <code>String</code> or struct cannot cross as a JavaScript string or object by itself.</p>
<p>Our front door takes a <code>&amp;str</code> and returns a <code>String</code>. Those are not numbers.</p>
<p>Someone has to write the code that turns a Rust <code>String</code> into a JavaScript string, one byte at a time, on both sides of the line. That code is tedious, and it is the same every time, so a tool writes it. Two tools, in fact:</p>
<ul>
<li><strong>wasm-bindgen</strong> is a Rust crate and a command-line program. The crate gives us an attribute to mark what JavaScript should be able to call. The program reads a compiled <code>.wasm</code> file and writes the JavaScript glue for everything marked.</li>
<li><strong>wasm-pack</strong> runs the whole build: <code>cargo build</code> for the WebAssembly target, then <code>wasm-bindgen</code>, then a shrinking pass, and puts the results in one folder, ready for a web page.</li>
</ul>
<p>Install <code>wasm-pack</code>:</p>
<pre><code class="language-bash">cargo install wasm-pack
</code></pre>
<p><code>cargo install</code> builds a program from <a href="http://crates.io">crates.io</a> and puts it on your path. This one takes a couple of minutes.</p>
<p>The other tool comes in through <code>Cargo.toml</code>, the way <code>serde</code> did. Open it and make two changes:</p>
<p><strong>Cargo.toml</strong></p>
<pre><code class="language-toml">[package]
name = "rustbound-idle"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
wasm-bindgen = "0.2"
</code></pre>
<p>The dependency is the familiar part.</p>
<p>The <code>[lib]</code> section is new. It tells Cargo what <em>shape</em> to build the library in.</p>
<p><code>rlib</code> is the shape Rust uses: it is what <code>main.rs</code> and the tests have been linking against all along. <code>cdylib</code> is a shape other languages can load. Think of a front door for the family and a side door for guests. We ask for both, so that <code>cargo run</code> and <code>cargo test</code> keep using the door they know, and <code>wasm-pack</code> gets one it can open.</p>
<p>Build:</p>
<pre><code class="language-bash">cargo build
</code></pre>
<p>Cargo downloads <code>wasm-bindgen</code> and a few helpers, compiles them, and finishes with nothing to say. Nothing is marked yet.</p>
<p>Run <code>cargo test</code> too. Twenty-eight.</p>
<hr />
<h2>Do not export the engine</h2>
<p>Here is the tempting move: put the <code>wasm-bindgen</code> attribute on <code>Game</code>, on <code>Player</code>, on <code>Enemy</code>, and let JavaScript have everything.</p>
<p>We are not going to do that, for the same reason <code>rules</code> is not on the front door.</p>
<p><code>Game</code> has never heard of a browser. The moment it carries a browser attribute, it has. Every future change to the engine would have to ask whether JavaScript still likes the shape. And JavaScript would be free to reach past the methods, read the fields, and start doing arithmetic of its own.</p>
<p>So we build an <strong>adapter</strong>: a small type that holds a <code>Game</code>, and shows JavaScript only what we choose.</p>
<p>It is a separate concern, so it gets a separate module. Create <code>src/web.rs</code>:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">use crate::game::Game;
use wasm_bindgen::prelude::*;

#[wasm_bindgen(js_name = Game)]
pub struct WebGame {
    inner: Game,
}

#[wasm_bindgen(js_class = Game)]
impl WebGame {
    #[wasm_bindgen(constructor)]
    pub fn new() -&gt; WebGame {
        WebGame { inner: Game::new() }
    }

    pub fn click(&amp;mut self) {
        self.inner.click();
    }

    pub fn tick(&amp;mut self, seconds: u32) {
        self.inner.tick(seconds);
    }
}
</code></pre>
<p>And register it. In <code>src/lib.rs</code>, add <code>mod web;</code> under <code>mod save;</code>. It does not go on the front door. JavaScript will not come in through <code>lib.rs</code>; it comes in through the glue.</p>
<p>Now read <code>web.rs</code> from the top.</p>
<p><code>use wasm_bindgen::prelude::*;</code> brings in everything the crate expects us to use. A <em>prelude</em> is a crate's "you will want all of these" list. The <code>*</code> means all of it.</p>
<p><code>#[wasm_bindgen]</code> is an attribute, like <code>#[derive(...)]</code> and <code>#[test]</code>: a note to the compiler, attached to the thing below it. This one says <em>JavaScript should be able to see this</em>.</p>
<p><code>WebGame</code> is the adapter. It has one field, <code>inner</code>, holding the real <code>Game</code>. The field is private. JavaScript cannot see inside, and neither can anything outside this file.</p>
<p><code>js_name = Game</code> on the struct, and <code>js_class = Game</code> on the <code>impl</code> block, say what the type is called <em>on the JavaScript side</em>. In Rust it is <code>WebGame</code>, so the two never collide. In the browser it is <code>Game</code>, because that is what it is.</p>
<p><code>#[wasm_bindgen(constructor)]</code> marks <code>new()</code> as the thing <code>new Game()</code> runs in JavaScript.</p>
<p><code>click()</code> and <code>tick()</code> are the smallest methods in the project. They hand the call to <code>inner</code> and that is all. That is what an adapter does. The rules stay on the far side.</p>
<p><strong>Question:</strong> Why does <code>WebGame</code> hold a private <code>Game</code> instead of exporting every engine field?</p>
<ol>
<li>Private fields make WebAssembly download faster</li>
<li>The adapter keeps JavaScript's interface small and leaves the rules in Rust</li>
<li>Rust structs cannot contain public fields</li>
</ol>
<p><strong>Answer:</strong> 2. The adapter keeps JavaScript's interface small and leaves the rules in Rust</p>
<blockquote>
<p><strong>Explanation:</strong> JavaScript gets a few operations chosen by the adapter. It cannot reach into the engine and start calculating a second version of a rule.</p>
</blockquote>
<p>Build it:</p>
<pre><code class="language-bash">cargo build
</code></pre>
<p>It compiles with nothing to say. On your own machine, <code>cargo build</code> still reads the attribute and checks that everything it marks <em>could</em> cross to JavaScript, which is about to matter. But it exports nothing. The result is a normal library, which is why <code>cargo test</code> keeps working, and we will lean on that before we are done.</p>
<hr />
<h2>Only some things can cross</h2>
<p><code>buy_click_upgrade()</code> is next. The engine's version returns a <code>PurchaseOutcome</code>. Try handing that straight across. Add this inside the <code>impl WebGame</code> block:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">    #[wasm_bindgen(js_name = buyClickUpgrade)]
    pub fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        self.inner.buy_click_upgrade()
    }
</code></pre>
<p>And add <code>use crate::player::PurchaseOutcome;</code> under the first <code>use</code> line.</p>
<p>The <code>js_name</code> attribute works on methods too. Rust calls it <code>buy_click_upgrade</code>, because that is how Rust names things. JavaScript calls it <code>buyClickUpgrade</code>, because that is how JavaScript names things. Each side gets its own convention and neither has to flinch.</p>
<p>Build, and Rust refuses, because this type is not allowed across:</p>
<pre><code class="language-text">error[E0277]: the trait bound `PurchaseOutcome: IntoWasmAbi` is not satisfied
  --&gt; src/web.rs:10:1
   |
10 | #[wasm_bindgen(js_class = Game)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound
   |
help: the trait `IntoWasmAbi` is not implemented for `PurchaseOutcome`
</code></pre>
<p>(There are several of these, and one names a cousin trait, <code>WasmDescribe</code>. They all say the same thing: this type cannot cross.)</p>
<p><code>IntoWasmAbi</code> is the trait for <em>can be handed to JavaScript</em>. Numbers have it. <code>bool</code> has it. <code>String</code> and <code>&amp;str</code> have it, thanks to the glue. Types marked with <code>#[wasm_bindgen]</code> have it.</p>
<p><code>PurchaseOutcome</code> does not, and we are not going to mark it, because it belongs to the engine.</p>
<p>So what crosses instead?</p>
<p><code>false</code> is still not an answer. We settled that in the seventh article, and a border does not change it. What crosses is the <em>name</em> of the answer, as text:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">    #[wasm_bindgen(js_name = buyClickUpgrade)]
    pub fn buy_click_upgrade(&amp;mut self) -&gt; String {
        match self.inner.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; String::from("purchased"),
            PurchaseOutcome::NotEnoughGold =&gt; String::from("notEnoughGold"),
        }
    }
</code></pre>
<p>Replace the version you just wrote with this one.</p>
<p>The <code>match</code> is doing the translating. Rust's enum stays an enum. JavaScript gets a string it can compare against. If a third outcome ever appears, the <code>match</code> refuses to compile until it has a name too, which is the same protection we had before.</p>
<p><strong>Question:</strong> What crosses into JavaScript when an upgrade cannot be bought?</p>
<ol>
<li>The whole <code>PurchaseOutcome</code> enum definition</li>
<li><code>false</code></li>
<li>The string <code>"notEnoughGold"</code></li>
</ol>
<p><strong>Answer:</strong> 3. The string <code>"notEnoughGold"</code></p>
<blockquote>
<p><strong>Explanation:</strong> The adapter translates the Rust enum variant into text. The engine keeps the enum, including Rust's exhaustive <code>match</code> check.</p>
</blockquote>
<p>Build, and it is quiet again.</p>
<hr />
<h2>A snapshot for the screen</h2>
<p>If JavaScript ever computes click damage, there are two programs that know how, and one day they will disagree.</p>
<p>That is the rule this whole section protects.</p>
<p>The browser is going to draw a screen, and a screen needs numbers: the stage, the gold, how much a click does, what the next upgrade costs. Some of those are fields. Some are rules. We could hand JavaScript the fields and let it work out the rest, and that is exactly the thing it must never do.</p>
<p>So the adapter answers all of it at once, as one piece of JSON, and Rust does every bit of arithmetic before the text leaves.</p>
<p>Add this at the bottom of <code>web.rs</code>, after the <code>impl WebGame</code> block:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Snapshot {
    stage: u32,
    gold: u32,
    xp: u32,
    xp_needed: u32,
    level: u32,
    click_damage: u32,
    click_upgrade_level: u32,
    click_upgrade_cost: u32,
    enemy_name: String,
    enemy_health: u32,
    enemy_max_health: u32,
}

impl Snapshot {
    fn of(game: &amp;Game) -&gt; Snapshot {
        Snapshot {
            stage: game.stage,
            gold: game.player.gold,
            xp: game.player.xp,
            xp_needed: game.player.xp_needed(),
            level: game.player.level,
            click_damage: game.player.click_damage(),
            click_upgrade_level: game.player.click_upgrade_level,
            click_upgrade_cost: game.player.click_upgrade_cost(),
            enemy_name: game.enemy.name.clone(),
            enemy_health: game.enemy.health,
            enemy_max_health: enemy_health_for_stage(game.stage),
        }
    }
}
</code></pre>
<p>And at the top of the file, two more <code>use</code> lines, under the one for <code>PurchaseOutcome</code>:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">use crate::rules::enemy_health_for_stage;
use serde::Serialize;
</code></pre>
<p>A <code>Snapshot</code> is a flat list of everything a screen might want, taken at one moment. It only needs <code>Serialize</code>, not <code>Deserialize</code>, because nobody ever reads a snapshot back in. It is a picture, not a save.</p>
<p>Three lines in <code>of()</code> deserve a look.</p>
<p><code>xp_needed</code>, <code>click_damage</code> and <code>click_upgrade_cost</code> are not copied. They are <em>asked for</em>. Each one is a rule, applied in Rust, and only the answer goes in the picture.</p>
<p><code>enemy_max_health</code> is the same idea. The enemy does not store its full health; the rule <code>enemy_health_for_stage()</code> knows it, and the stage is right there.</p>
<p><code>game.enemy.name.clone()</code> is new. A <code>String</code> owns its text, and two things cannot own the same text at once. The enemy owns that name. The snapshot wants its own, so <code>.clone()</code> makes a second copy. (The <code>u32</code> fields do not need this. A number is small enough that Rust copies it without asking.)</p>
<p>And the attribute above the struct:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">#[serde(rename_all = "camelCase")]
</code></pre>
<p>This is the article where JavaScript finally reads our JSON, so this is the article where the names change shape. Rust writes <code>click_damage</code>. JavaScript expects <code>clickDamage</code>. <code>rename_all</code> tells <code>serde</code> to convert every field name on the way out, and the Rust code stays the way Rust likes it.</p>
<p>The save is not getting this. JavaScript stores a save and hands it back; it never looks inside. Changing the save's field names would be a new save version for the benefit of nobody.</p>
<p>Two small things remain before it builds.</p>
<p><code>click_upgrade_cost()</code> does not exist on <code>Player</code> yet. The cost has only ever been needed <em>inside</em> a purchase. Now a screen wants to show it. In <code>src/player.rs</code>, add a method under <code>click_damage()</code>:</p>
<p><strong>src/player.rs</strong></p>
<pre><code class="language-rust">    pub fn click_upgrade_cost(&amp;self) -&gt; u32 {
        click_upgrade_cost(self.click_upgrade_level)
    }
</code></pre>
<p>And since the player can answer that now, <code>buy_click_upgrade()</code> should ask instead of reaching for the rule. Change its first line from:</p>
<p><strong>src/player.rs</strong></p>
<pre><code class="language-rust">        let cost = click_upgrade_cost(self.click_upgrade_level);
</code></pre>
<p>to:</p>
<p><strong>src/player.rs</strong></p>
<pre><code class="language-rust">        let cost = self.click_upgrade_cost();
</code></pre>
<p>Then the method that hands the picture over. Back in <code>web.rs</code>, inside <code>impl WebGame</code>, under <code>buy_click_upgrade()</code>:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">    #[wasm_bindgen(js_name = stateJson)]
    pub fn state_json(&amp;self) -&gt; String {
        let snapshot = Snapshot::of(&amp;self.inner);

        serde_json::to_string(&amp;snapshot).expect("a Snapshot can always be written as JSON")
    }
</code></pre>
<p>That <code>expect()</code> is the same promise <code>save_json()</code> makes, for the same reason: a struct of numbers and one string cannot fail to become JSON.</p>
<p><strong>Question:</strong> Why does the snapshot contain <code>clickDamage</code> instead of making JavaScript calculate it?</p>
<ol>
<li>JavaScript cannot add two numbers</li>
<li>Rust should remain the only place that knows the damage rule</li>
<li>JSON requires every number to be calculated twice</li>
</ol>
<p><strong>Answer:</strong> 2. Rust should remain the only place that knows the damage rule</p>
<blockquote>
<p><strong>Explanation:</strong> The screen receives the answer it needs. If JavaScript repeated the formula, the two versions could disagree after a later rule change.</p>
</blockquote>
<p>Build. Quiet.</p>
<hr />
<h2>Saves cross too</h2>
<p>Saving is already text, so it crosses as it is:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">    #[wasm_bindgen(js_name = saveJson)]
    pub fn save_json(&amp;self) -&gt; String {
        self.inner.save_json()
    }
</code></pre>
<p>Loading is the interesting one, because loading can fail, and now the failure has to cross a border.</p>
<p>Try the obvious thing first:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">    #[wasm_bindgen(js_name = fromJson)]
    pub fn from_json(json: &amp;str) -&gt; Result&lt;WebGame, LoadError&gt; {
        match Game::from_saved_json(json) {
            Ok(inner) =&gt; Ok(WebGame { inner }),
            Err(error) =&gt; Err(error),
        }
    }
</code></pre>
<p>Add <code>use crate::save::LoadError;</code> to the <code>use</code> lines, and build. Rust refuses again, and this time the complaint is about the error half:</p>
<pre><code class="language-text">error[E0277]: the trait bound `Result&lt;WebGame, LoadError&gt;: ReturnWasmAbi` is not satisfied
   --&gt; src/web.rs:13:1
    |
 13 | #[wasm_bindgen(js_class = Game)]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ReturnWasmAbi` is not implemented for `Result&lt;WebGame, LoadError&gt;`
    |
help: the trait `ReturnWasmAbi` is implemented for `Result&lt;T, E&gt;`
    |
779 | / impl&lt;T, E&gt; ReturnWasmAbi for Result&lt;T, E&gt;
780 | | where
781 | |     T: IntoWasmAbi,
782 | |     E: Into&lt;JsValue&gt;,
</code></pre>
<p>Same story as the purchase, with one new piece of information at the bottom.</p>
<p>A <code>Result</code> <em>can</em> cross, as long as both halves can. <code>WebGame</code> is fine. <code>LoadError</code> is not, and the <code>help</code> says what the error half needs to be: something that can become a <code>JsValue</code>, which is the crate's word for <em>any JavaScript value</em>.</p>
<p>A <code>String</code> can. So the adapter translates the error the same way it translated the purchase: into words.</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">    #[wasm_bindgen(js_name = fromJson)]
    pub fn from_json(json: &amp;str) -&gt; Result&lt;WebGame, String&gt; {
        match Game::from_saved_json(json) {
            Ok(inner) =&gt; Ok(WebGame { inner }),
            Err(LoadError::Unreadable) =&gt; Err(String::from("That save could not be read.")),
            Err(LoadError::UnsupportedVersion(version)) =&gt; Err(format!(
                "That save is version {version}, and this game cannot read it."
            )),
        }
    }
</code></pre>
<p><code>main()</code> prints a failed load with <code>{error:?}</code>, which shows the variant's name: <code>Could not load the save: Unreadable</code>. That is fine in a terminal only we read. A player deserves a sentence, so the adapter writes one for each way a load can fail.</p>
<p><code>format!()</code> is <code>println!()</code>'s quieter sibling. Same curly braces, but instead of printing the result, it hands it back as a <code>String</code>.</p>
<p>What does JavaScript see when the <code>Result</code> is an <code>Err</code>? The glue <em>throws</em> it. On the other side, <code>Game.fromJson(text)</code> either returns a game or throws the message, and a <code>try</code>/<code>catch</code> catches it. We will use that next time, when there is a save to load.</p>
<p>Build. Quiet. Here is <code>web.rs</code> so far, for checking against. The <code>Snapshot</code> code at the bottom is unchanged:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">use crate::game::Game;
use crate::player::PurchaseOutcome;
use crate::rules::enemy_health_for_stage;
use crate::save::LoadError;
use serde::Serialize;
use wasm_bindgen::prelude::*;

#[wasm_bindgen(js_name = Game)]
pub struct WebGame {
    inner: Game,
}

#[wasm_bindgen(js_class = Game)]
impl WebGame {
    #[wasm_bindgen(constructor)]
    pub fn new() -&gt; WebGame {
        WebGame { inner: Game::new() }
    }

    pub fn click(&amp;mut self) {
        self.inner.click();
    }

    pub fn tick(&amp;mut self, seconds: u32) {
        self.inner.tick(seconds);
    }

    #[wasm_bindgen(js_name = buyClickUpgrade)]
    pub fn buy_click_upgrade(&amp;mut self) -&gt; String {
        match self.inner.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; String::from("purchased"),
            PurchaseOutcome::NotEnoughGold =&gt; String::from("notEnoughGold"),
        }
    }

    #[wasm_bindgen(js_name = stateJson)]
    pub fn state_json(&amp;self) -&gt; String {
        let snapshot = Snapshot::of(&amp;self.inner);

        serde_json::to_string(&amp;snapshot).expect("a Snapshot can always be written as JSON")
    }

    #[wasm_bindgen(js_name = saveJson)]
    pub fn save_json(&amp;self) -&gt; String {
        self.inner.save_json()
    }

    #[wasm_bindgen(js_name = fromJson)]
    pub fn from_json(json: &amp;str) -&gt; Result&lt;WebGame, String&gt; {
        match Game::from_saved_json(json) {
            Ok(inner) =&gt; Ok(WebGame { inner }),
            Err(LoadError::Unreadable) =&gt; Err(String::from("That save could not be read.")),
            Err(LoadError::UnsupportedVersion(version)) =&gt; Err(format!(
                "That save is version {version}, and this game cannot read it."
            )),
        }
    }
}
</code></pre>
<p>Seven things JavaScript can do. That is the whole border.</p>
<hr />
<h2>Test it where you are</h2>
<p>We have not compiled for WebAssembly yet, and we can already test the adapter, because on this machine it is ordinary Rust.</p>
<p>Add a <code>tests</code> module at the bottom of <code>web.rs</code>:</p>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_new_game_reports_its_starting_state() {
        let game = WebGame::new();

        assert_eq!(
            game.state_json(),
            r#"{"stage":1,"gold":0,"xp":0,"xpNeeded":20,"level":1,"clickDamage":5,"clickUpgradeLevel":0,"clickUpgradeCost":10,"enemyName":"Training Slime","enemyHealth":20,"enemyMaxHealth":20}"#
        );
    }

    #[test]
    fn a_purchase_answers_in_words() {
        let mut game = WebGame::new();

        assert_eq!(game.buy_click_upgrade(), "notEnoughGold");

        game.tick(100);

        assert_eq!(game.buy_click_upgrade(), "purchased");
    }

    #[test]
    fn a_save_crosses_the_border_and_back() {
        let mut game = WebGame::new();
        game.tick(600);

        let save = game.save_json();
        let restored = WebGame::from_json(&amp;save).expect("the save should load");

        assert_eq!(restored.state_json(), game.state_json());
    }

    #[test]
    fn nonsense_is_refused_at_the_border() {
        assert_eq!(
            WebGame::from_json("banana").err(),
            Some(String::from("That save could not be read."))
        );
    }
}
</code></pre>
<p>The first test is the one to read slowly. That is the exact text a browser will receive for a new game: every name in camelCase, every rule already applied. Click damage is 5 because level 1 hits for 5. The upgrade costs 10. The slime has 20 of 20.</p>
<p>The last test uses <code>.err()</code>, which turns a <code>Result</code> into an <code>Option</code>: <code>Some(error)</code> if it failed, <code>None</code> if it did not. We cannot <code>assert_eq!</code> on the whole <code>Result</code>, because <code>WebGame</code> has no <code>PartialEq</code> and does not need one. Comparing just the error half is enough.</p>
<p>Run them:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>Thirty-two. Thirty in the library, and the two outside tests.</p>
<p>If you see twenty-eight, the <code>mod web;</code> line is missing from <code>lib.rs</code>. Without it, the file is not part of the library at all, and nothing complains until much later, in the browser. Add the line and run the tests again.</p>
<hr />
<h2>Build for the browser</h2>
<p>Now the other machine.</p>
<pre><code class="language-bash">wasm-pack build --target web --out-dir web/pkg
</code></pre>
<p><code>--target web</code> asks for glue that a plain web page can import, with no bundler and no Node. <code>--out-dir web/pkg</code> says where to put it.</p>
<p>The first run is slow. <code>wasm-pack</code> fetches the <code>wasm-bindgen</code> command-line program to match the crate version in <code>Cargo.lock</code>, compiles the library for <code>wasm32-unknown-unknown</code> in release mode, runs the glue generator, and shrinks the result. After that it is a few seconds.</p>
<p>When it finishes, the last lines look like this:</p>
<pre><code class="language-text">[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: :-) Done in 24.46s
[INFO]: :-) Your wasm pkg is ready to publish at /home/you/rustbound-idle/web/pkg.
</code></pre>
<p>Your time and path will differ, and the note about optional fields is safe to ignore. Look in the folder:</p>
<pre><code class="language-text">web/pkg/
├── .gitignore
├── package.json
├── rustbound_idle.d.ts
├── rustbound_idle.js
├── rustbound_idle_bg.wasm
└── rustbound_idle_bg.wasm.d.ts
</code></pre>
<p><code>rustbound_idle_bg.wasm</code> is the compiled game, about 110 KB. (We will look at that number again when it is time to ship.) <code>rustbound_idle.js</code> is the glue: the JavaScript that loads the <code>.wasm</code> file and wraps every marked method so that strings go in and out as strings. <code>package.json</code> describes the folder to JavaScript tooling we are not using.</p>
<p>The two <code>.d.ts</code> files are the ones to open. They are written for TypeScript, a version of JavaScript with types, but you do not need to know TypeScript to read this; it is a list of everything JavaScript can call, and it is the clearest summary of what we just built. The <code>Game</code> class is near the top of <code>rustbound_idle.d.ts</code>; everything below it is glue you can ignore:</p>
<p><strong>web/pkg/rustbound_idle.d.ts</strong></p>
<pre><code class="language-ts">export class Game {
    free(): void;
    [Symbol.dispose](): void;
    buyClickUpgrade(): string;
    click(): void;
    static fromJson(json: string): Game;
    constructor();
    saveJson(): string;
    stateJson(): string;
    tick(seconds: number): void;
}
</code></pre>
<p>Every <code>js_name</code> we chose is in there. <code>fromJson</code> is <code>static</code>, because it makes a <code>Game</code> rather than being called on one, which is the same reason <code>from_saved_json()</code> was an associated function.</p>
<p><code>free()</code> and <code>[Symbol.dispose]</code> are the two we did not write. A <code>Game</code> on the Rust side lives in WebAssembly's memory, and JavaScript's garbage collector cannot see in there. <code>free()</code> is how JavaScript gives a game back when it is done with it, and the other is a newer spelling of the same thing. We will not need either today.</p>
<p>The <code>.gitignore</code> inside <code>pkg/</code> contains a single <code>*</code>. The whole folder is generated, like <code>target/</code>, and it stays out of version control.</p>
<p>If instead you see this:</p>
<pre><code class="language-text">Error: crate-type must be cdylib to compile to WebAssembly. Add the following to your Cargo.toml file:

[lib]
crate-type = ["cdylib", "rlib"]
</code></pre>
<p>then the <code>[lib]</code> section did not make it into <code>Cargo.toml</code>. Add it and run the build again.</p>
<p>And if the build succeeds but the <code>.d.ts</code> file has no <code>Game</code> class in it, <code>mod web;</code> is missing from <code>lib.rs</code>. The page will later complain that the module "does not provide an export named 'Game'", which is the same problem seen from the other side.</p>
<hr />
<h2>A page that runs the engine</h2>
<p>Create <code>web/index.html</code>, next to the <code>pkg</code> folder:</p>
<p><strong>web/index.html</strong></p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;title&gt;Rustbound Idle&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;Rustbound Idle&lt;/h1&gt;
    &lt;pre id="output"&gt;Loading the engine...&lt;/pre&gt;

    &lt;script type="module"&gt;
      import init, { Game } from "./pkg/rustbound_idle.js";

      await init();

      const game = new Game();
      const before = JSON.parse(game.stateJson());

      game.tick(3600);

      const after = JSON.parse(game.stateJson());

      document.querySelector("#output").textContent =
        `One hour away.\nStage ${before.stage} -&gt; ${after.stage}  ` +
        `Level ${before.level} -&gt; ${after.level}  ` +
        `Gold ${before.gold} -&gt; ${after.gold}`;
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>The script is the whole point, so read it line by line.</p>
<p><code>import init, { Game } from "./pkg/rustbound_idle.js";</code> brings in two things from the glue. <code>Game</code> is our class. <code>init</code> is a function that fetches <code>rustbound_idle_bg.wasm</code> and loads it into the browser.</p>
<p><code>await init();</code> does that loading. It is the one step that takes time, which is why the page says "Loading the engine..." until it is done. (If you have been told <code>await</code> only works inside an <code>async</code> function: at the top of a <code>&lt;script type="module"&gt;</code> it is allowed. That is one of the things <code>type="module"</code> buys us.)</p>
<p><code>new Game()</code> runs our constructor. <code>game.stateJson()</code> runs our snapshot. <code>game.tick(3600)</code> runs the same loop that has run in every test since the eighth article.</p>
<p><code>JSON.parse()</code> turns our text into a JavaScript object, so <code>before.stage</code> and <code>before.gold</code> just work. The names are camelCase, and that is <code>rename_all</code> paying off.</p>
<p>The sandbox below exercises only that browser-side handoff. It does not pretend to compile or load the generated <code>.wasm</code> package. Run it once, then change the three values in <code>after</code> and run it again. The sentence should follow the snapshot without learning a single game rule.</p>
<p><strong>HTML</strong></p>
<pre><code class="language-html">&lt;h2&gt;Rustbound Idle&lt;/h2&gt;
&lt;pre id="output"&gt;Waiting for a snapshot...&lt;/pre&gt;
</code></pre>
<p><strong>JavaScript</strong></p>
<pre><code class="language-js">const snapshotText = '{"stage":1,"gold":0,"xp":0,"xpNeeded":20,"level":1,"clickDamage":5,"clickUpgradeLevel":0,"clickUpgradeCost":10,"enemyName":"Training Slime","enemyHealth":20,"enemyMaxHealth":20}';

const before = JSON.parse(snapshotText);
const after = {
  ...before,
  stage: 128,
  level: 47,
  gold: 16637,
};

document.querySelector("#output").textContent =
  `One hour away.\nStage ${before.stage} -&gt; ${after.stage}  ` +
  `Level ${before.level} -&gt; ${after.level}  ` +
  `Gold ${before.gold} -&gt; ${after.gold}`;
</code></pre>
<p><a href="https://mnixblog.netlify.app/rust-game-webassembly-wasm-bindgen/">Live example · render the Rust snapshot on the canonical article</a>.</p>
<p>Then the last lines print the same sentence <code>main()</code> prints. Everything else on the page is scaffolding.</p>
<p>Now serve it. A browser will not load a module or a <code>.wasm</code> file from a plain file on disk; both have to come from a web server, even a trivial one. Python ships with a trivial one:</p>
<pre><code class="language-bash">python3 -m http.server 8080 --directory web
</code></pre>
<p>(On Windows the command is usually <code>python</code> rather than <code>python3</code>.)</p>
<p>Open <code>http://localhost:8080/</code> in a browser.</p>
<pre><code class="language-text">Rustbound Idle

One hour away.
Stage 1 -&gt; 128  Level 1 -&gt; 47  Gold 0 -&gt; 16637
</code></pre>
<p>Stage 128. Level 47.</p>
<p>Those are the numbers in <code>tests/idle_hour.rs</code>, and they have been since the ninth article. The test runs on your machine, in Rust. The page runs in a browser, in WebAssembly, called from JavaScript. Same library. Same answer.</p>
<p>If you open <code>index.html</code> directly instead, by double-clicking it, the page stays on "Loading the engine..." and the browser console says something like:</p>
<pre><code class="language-text">Access to script at 'file:///.../web/pkg/rustbound_idle.js' from origin 'null'
has been blocked by CORS policy: Cross origin requests are only supported for
protocol schemes: chrome, chrome-untrusted, data, http, https.
</code></pre>
<p>That is the browser refusing to load modules from <code>file://</code>. It is not a bug in our code. Use the server.</p>
<p>One habit to pick up now: after you change the Rust and rebuild, browsers like to keep serving the old <code>.wasm</code> file. If a number on the page does not change when you expected it to, do a hard refresh (Ctrl+Shift+R, or Cmd+Shift+R on a Mac) before you go looking for a bug.</p>
<p>One more thing to notice: <code>cargo run</code> still works. Try it. The terminal version prints its three stages and its hour, exactly as before. We did not replace a program. We gave the same library a second door.</p>
<hr />
<h2>The complete program</h2>
<p>If you are only reading along, skip to the last two sections.</p>
<p>If you have been following along, here is everything that changed. <code>src/rules.rs</code>, <code>src/enemy.rs</code>, <code>src/game.rs</code>, <code>src/save.rs</code>, <code>src/main.rs</code>, and the two files in <code>tests/</code> are exactly as they were in the starting point.</p>
<p>The project now looks like this:</p>
<pre><code class="language-text">rustbound-idle/
├── Cargo.toml
├── Cargo.lock
├── src/
│   ├── lib.rs
│   ├── main.rs
│   ├── rules.rs
│   ├── enemy.rs
│   ├── player.rs
│   ├── game.rs
│   ├── save.rs
│   └── web.rs
├── tests/
│   ├── idle_hour.rs
│   └── save_and_restore.rs
└── web/
    ├── index.html
    └── pkg/            (generated by wasm-pack)
</code></pre>
<p><code>Cargo.toml</code>:</p>
<p><strong>Cargo.toml</strong></p>
<pre><code class="language-toml">[package]
name = "rustbound-idle"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
wasm-bindgen = "0.2"
</code></pre>
<p><code>src/lib.rs</code>:</p>
<p><strong>src/lib.rs</strong></p>
<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;
mod save;
mod web;

pub use enemy::Enemy;
pub use game::Game;
pub use player::{Player, PurchaseOutcome};
pub use save::LoadError;
</code></pre>
<p>Only the <code>impl</code> block in <code>src/player.rs</code> changes; the struct, the enum, and the tests are as they were.</p>
<details>
<summary><code>src/player.rs</code> · changed 56-line <code>impl</code></summary>
<p><strong>src/player.rs</strong></p>
<pre><code class="language-rust">impl Player {
    pub(crate) fn new() -&gt; Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }

<pre><code>pub(crate) fn add_gold(&amp;mut self, amount: u32) {
    self.gold = self.gold.saturating_add(amount);
}

pub(crate) fn add_xp(&amp;mut self, amount: u32) {
    self.xp = self.xp.saturating_add(amount);

    while self.xp &gt;= self.xp_needed() {
        self.xp -= self.xp_needed();
        self.level = self.level.saturating_add(1);
    }
}

pub fn xp_needed(&amp;self) -&gt; u32 {
    xp_to_next_level(self.level)
}

pub fn click_damage(&amp;self) -&gt; u32 {
    click_damage(self.base_damage(), self.bonus_damage())
}

pub fn click_upgrade_cost(&amp;self) -&gt; u32 {
    click_upgrade_cost(self.click_upgrade_level)
}

fn base_damage(&amp;self) -&gt; u32 {
    base_damage_for_level(self.level)
}

fn bonus_damage(&amp;self) -&gt; u32 {
    click_upgrade_bonus(self.click_upgrade_level)
}

pub(crate) fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
    let cost = self.click_upgrade_cost();

    if self.gold &lt; cost {
        return PurchaseOutcome::NotEnoughGold;
    }

    self.gold -= cost;
    self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

    PurchaseOutcome::Purchased
}
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<details>
<summary><code>src/web.rs</code> · 137 lines</summary>
<p><strong>src/web.rs</strong></p>
<pre><code class="language-rust">use crate::game::Game;
use crate::player::PurchaseOutcome;
use crate::rules::enemy_health_for_stage;
use crate::save::LoadError;
use serde::Serialize;
use wasm_bindgen::prelude::*;

<p>#[wasm_bindgen(js_name = Game)]
pub struct WebGame {
    inner: Game,
}</p>
<p>#[wasm_bindgen(js_class = Game)]
impl WebGame {
    #[wasm_bindgen(constructor)]
    pub fn new() -&gt; WebGame {
        WebGame { inner: Game::new() }
    }</p>
<pre><code>pub fn click(&amp;mut self) {
    self.inner.click();
}

pub fn tick(&amp;mut self, seconds: u32) {
    self.inner.tick(seconds);
}

#[wasm_bindgen(js_name = buyClickUpgrade)]
pub fn buy_click_upgrade(&amp;mut self) -&gt; String {
    match self.inner.buy_click_upgrade() {
        PurchaseOutcome::Purchased =&gt; String::from("purchased"),
        PurchaseOutcome::NotEnoughGold =&gt; String::from("notEnoughGold"),
    }
}

#[wasm_bindgen(js_name = stateJson)]
pub fn state_json(&amp;self) -&gt; String {
    let snapshot = Snapshot::of(&amp;self.inner);

    serde_json::to_string(&amp;snapshot).expect("a Snapshot can always be written as JSON")
}

#[wasm_bindgen(js_name = saveJson)]
pub fn save_json(&amp;self) -&gt; String {
    self.inner.save_json()
}

#[wasm_bindgen(js_name = fromJson)]
pub fn from_json(json: &amp;str) -&gt; Result&lt;WebGame, String&gt; {
    match Game::from_saved_json(json) {
        Ok(inner) =&gt; Ok(WebGame { inner }),
        Err(LoadError::Unreadable) =&gt; Err(String::from("That save could not be read.")),
        Err(LoadError::UnsupportedVersion(version)) =&gt; Err(format!(
            "That save is version {version}, and this game cannot read it."
        )),
    }
}
</code></pre>
<p>}</p>
<p>#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Snapshot {
    stage: u32,
    gold: u32,
    xp: u32,
    xp_needed: u32,
    level: u32,
    click_damage: u32,
    click_upgrade_level: u32,
    click_upgrade_cost: u32,
    enemy_name: String,
    enemy_health: u32,
    enemy_max_health: u32,
}</p>
<p>impl Snapshot {
    fn of(game: &amp;Game) -&gt; Snapshot {
        Snapshot {
            stage: game.stage,
            gold: game.player.gold,
            xp: game.player.xp,
            xp_needed: game.player.xp_needed(),
            level: game.player.level,
            click_damage: game.player.click_damage(),
            click_upgrade_level: game.player.click_upgrade_level,
            click_upgrade_cost: game.player.click_upgrade_cost(),
            enemy_name: game.enemy.name.clone(),
            enemy_health: game.enemy.health,
            enemy_max_health: enemy_health_for_stage(game.stage),
        }
    }
}</p>
<p>#[cfg(test)]
mod tests {
    use super::*;</p>
<pre><code>#[test]
fn a_new_game_reports_its_starting_state() {
    let game = WebGame::new();

    assert_eq!(
        game.state_json(),
        r#"{"stage":1,"gold":0,"xp":0,"xpNeeded":20,"level":1,"clickDamage":5,"clickUpgradeLevel":0,"clickUpgradeCost":10,"enemyName":"Training Slime","enemyHealth":20,"enemyMaxHealth":20}"#
    );
}

#[test]
fn a_purchase_answers_in_words() {
    let mut game = WebGame::new();

    assert_eq!(game.buy_click_upgrade(), "notEnoughGold");

    game.tick(100);

    assert_eq!(game.buy_click_upgrade(), "purchased");
}

#[test]
fn a_save_crosses_the_border_and_back() {
    let mut game = WebGame::new();
    game.tick(600);

    let save = game.save_json();
    let restored = WebGame::from_json(&amp;save).expect("the save should load");

    assert_eq!(restored.state_json(), game.state_json());
}

#[test]
fn nonsense_is_refused_at_the_border() {
    assert_eq!(
        WebGame::from_json("banana").err(),
        Some(String::from("That save could not be read."))
    );
}
</code></pre>
<p>}
</p></code></pre><p></p>
</details>

<details>
<summary><code>web/index.html</code> · 29 lines</summary>
<p><strong>web/index.html</strong></p>
<pre><code class="language-html">

  
    
    Rustbound Idle
  
  
    <h1>Rustbound Idle</h1>
    <pre id="output">Loading the engine...</pre>

<pre><code>&lt;script type="module"&gt;
  import init, { Game } from "./pkg/rustbound_idle.js";

  await init();

  const game = new Game();
  const before = JSON.parse(game.stateJson());

  game.tick(3600);

  const after = JSON.parse(game.stateJson());

  document.querySelector("#output").textContent =
    `One hour away.\nStage ${before.stage} -&gt; ${after.stage}  ` +
    `Level ${before.level} -&gt; ${after.level}  ` +
    `Gold ${before.gold} -&gt; ${after.gold}`;
&lt;/script&gt;
</code></pre>
  

</code></pre>
</details>

<p>You do not need to add <code>web/pkg</code> to your own <code>.gitignore</code>. The one <code>wasm-pack</code> wrote inside it already keeps the folder out.</p>
<p>Run all three:</p>
<pre><code class="language-bash">cargo test
wasm-pack build --target web --out-dir web/pkg
python3 -m http.server 8080 --directory web
</code></pre>
<p>Thirty-two tests pass, the package builds, and the page at <code>http://localhost:8080/</code> shows the hour away. Clippy should be quiet.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li>compile targets, and <code>wasm32-unknown-unknown</code></li>
<li>what WebAssembly can and cannot do on its own</li>
<li><code>wasm-bindgen</code> and <code>wasm-pack</code></li>
<li><code>cargo install</code></li>
<li><code>[lib] crate-type</code>, and the difference between <code>rlib</code> and <code>cdylib</code></li>
<li><code>#[wasm_bindgen]</code>, <code>js_name</code>, <code>js_class</code>, and <code>constructor</code></li>
<li>a prelude, and <code>use ...::*</code></li>
<li>an adapter that holds the engine instead of exposing it</li>
<li>which types can cross a language border, and translating the ones that cannot</li>
<li>a snapshot that applies every rule before the text leaves Rust</li>
<li><code>#[serde(rename_all = "camelCase")]</code></li>
<li><code>.clone()</code> for a <code>String</code></li>
<li><code>format!()</code></li>
<li>a <code>Result</code> crossing into JavaScript as a return or a throw</li>
<li><code>.err()</code> on a <code>Result</code></li>
<li>testing a WebAssembly adapter as plain Rust</li>
<li>ES modules, <code>import</code>, <code>await init()</code>, and why <code>file://</code> will not do</li>
</ul>
<p>You do not need to memorize all of that yet.</p>
<p>Two ideas are worth keeping.</p>
<p>The first is that the border is a translation, not a window. Nothing on the far side can see <code>Game</code>. It sees seven operations, each of which hands numbers or text across, and every rule has already been applied by the time the text leaves. The <code>match</code> in <code>buy_click_upgrade()</code> and the <code>match</code> in <code>from_json()</code> are the only places the two worlds touch, and both of them turn a Rust enum into words.</p>
<p>The second is that the hard part was already done. This article added one file to the library, one line to <code>lib.rs</code>, a four-line method to <code>player.rs</code>, and four lines of configuration. Everything else was two articles ago, when we decided <code>main()</code> should not know any formulas, and last time, when we decided a save should be text. A library that never printed, never asked the time, and never opened a file turned out to be a library that compiles for a machine with none of those things.</p>
<p>That is what the wall was for.</p>
<hr />
<h2>Something to click</h2>
<p>Our engine runs in a browser now. Nobody can play it.</p>
<p>There is no button. There is no screen. There is a <code>&lt;pre&gt;</code> tag and a number.</p>
<p>Next time, we put a face on it: one custom HTML element, <code>&lt;idle-game&gt;</code>, that draws the snapshot, turns a click on a button into <code>game.click()</code>, keeps the clock, and saves to the browser's storage so that closing the tab is not game over either.</p>
<p>The browser will own time and storage and pixels.</p>
<p>It still will not get the rules.</p>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Closing the Terminal Is Not Game Over.]]></title><description><![CDATA[Teach a Rust idle game to save itself as JSON and load itself back. Add your first dependency with serde, and let Result handle a save that will not load.]]></description><link>https://journeytocode.hashnode.dev/rust-save-load-serde-result</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/rust-save-load-serde-result</guid><category><![CDATA[Rust]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Wed, 28 Oct 2026 23:58:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/d31d810a-c3b7-4cb5-a09b-90860f7b9d50.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our game can simulate an hour without making us wait an hour.</p>
<p>It cannot survive the program ending.</p>
<p>Close the terminal and everything is gone: the stages, the gold, the levels, the upgrade you bought. The next <code>cargo run</code> starts at stage 1 with a fresh slime, as if nothing ever happened.</p>
<p>An idle game that forgets you the moment you leave is not much of an idle game.</p>
<p>I learned what lost progress feels like on a 386, building DOOM maps. It crashed in the middle of one and took an entire afternoon of carefully placed monsters and secret rooms with it. Nothing has taught me to save my work quite like that afternoon.</p>
<p>Our game deserves better than my DOOM maps got.</p>
<p>Today the game learns to write itself down, and to read itself back.</p>
<p>Not to a file, or a browser, or a server. The library will not know where saves go, and it should not. It will only know how to say, in text:</p>
<blockquote>
<p>This is everything about me.</p>
</blockquote>
<p>And, handed that same text later:</p>
<blockquote>
<p>I remember.</p>
</blockquote>
<p>By the end, <code>main()</code> clears three stages, saves, throws the game away, loads the save, and <em>then</em> spends the hour away:</p>
<pre><code class="language-text">Saved: {"save_version":1,"stage":4,"player":{"gold":11,"xp":4,"level":2,"click_upgrade_level":1},"enemy_health":50}
Loaded. Stage 4, level 2.
One hour away.
Stage 4 -&gt; 131  Level 2 -&gt; 48  Gold 11 -&gt; 17410
</code></pre>
<p>Same hour. Same numbers. A different game object, rebuilt from a line of text.</p>
<p>Saving always works. Loading might not: the text could be anything. So Rust hands the loaded game back in a box that holds one of two things, the game or the reason there is no game, and will not let us reach for the game until we have said what happens if it is the other one.</p>
<p>Along the way we add our first dependency, and that box turns out to be how Rust handles everything that can fail. There are five quick knowledge checks, two live examples you can edit and run in the page, and a coding challenge.</p>
<hr />
<h2>Our starting point</h2>
<p>This series builds a small idle game: a player clicks to fight slimes, earns gold and XP, and keeps progressing while you are away.</p>
<p>If you are only reading along, skip ahead to "A save is just text." The code is folded away below, one file at a time.</p>
<p>If you are building along, you need the library we built last time: a project called <code>rustbound-idle</code> with six source files and one outside test. (If you kept your project from the last article, you already have all of this. The <code>article_9</code> branch of the companion repository has it too.)</p>
<pre><code class="language-text">rustbound-idle/
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── main.rs
│   ├── rules.rs
│   ├── enemy.rs
│   ├── player.rs
│   └── game.rs
└── tests/
    └── idle_hour.rs
</code></pre>
<p><code>Cargo.toml</code> is whatever <code>cargo new rustbound-idle</code> gave you. It should say <code>edition = "2024"</code>, which needs Rust 1.85 or newer.</p>
<details>
<summary><code>src/lib.rs</code> · 8 lines</summary>

<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;

pub use enemy::Enemy;
pub use game::Game;
pub use player::{Player, PurchaseOutcome};
</code></pre>
</details>

<details>
<summary><code>src/main.rs</code> · 56 lines</summary>

<pre><code class="language-rust">use rustbound_idle::{Game, PurchaseOutcome};

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 3 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                game.player.xp_needed()
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    game.player.click_damage()
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }

        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                game.player.click_damage(),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold =&gt; {}
        }
    }

    let stage = game.stage;
    let level = game.player.level;
    let gold = game.player.gold;

    game.tick(3600);

    println!("One hour away.");
    println!(
        "Stage {} -&gt; {}  Level {} -&gt; {}  Gold {} -&gt; {}",
        stage, game.stage, level, game.player.level, gold, game.player.gold
    );
}
</code></pre>
</details>

<details>
<summary><code>src/rules.rs</code> · 105 lines</summary>

<pre><code class="language-rust">pub(crate) fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

pub(crate) fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

pub(crate) fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

pub(crate) fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

pub(crate) fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}

pub(crate) fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}

pub(crate) fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

pub(crate) fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}

pub(crate) fn idle_damage_for_level(level: u32) -&gt; u32 {
    level
}

pub(crate) fn click_upgrade_bonus(upgrades: u32) -&gt; u32 {
    upgrades.saturating_mul(2)
}

pub(crate) fn click_upgrade_cost(upgrades: u32) -&gt; u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn idle_damage_grows_with_level() {
        assert_eq!(idle_damage_for_level(1), 1);
        assert_eq!(idle_damage_for_level(2), 2);
    }

    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }
}
</code></pre>
</details>

<details>
<summary><code>src/enemy.rs</code> · 19 lines</summary>

<pre><code class="language-rust">use crate::rules::{enemy_health_for_stage, gold_reward_for_stage, xp_reward_for_stage};

pub struct Enemy {
    pub name: String,
    pub health: u32,
    pub(crate) gold_reward: u32,
    pub(crate) xp_reward: u32,
}

impl Enemy {
    pub(crate) fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}
</code></pre>
</details>

<details>
<summary><code>src/player.rs</code> · 114 lines</summary>

<pre><code class="language-rust">use crate::rules::{
    base_damage_for_level, click_damage, click_upgrade_bonus, click_upgrade_cost, xp_to_next_level,
};

pub struct Player {
    pub gold: u32,
    pub xp: u32,
    pub level: u32,
    pub click_upgrade_level: u32,
}

#[derive(Debug, PartialEq)]
pub enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}

impl Player {
    pub(crate) fn new() -&gt; Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }

    pub(crate) fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    pub(crate) fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= self.xp_needed() {
            self.xp -= self.xp_needed();
            self.level = self.level.saturating_add(1);
        }
    }

    pub fn xp_needed(&amp;self) -&gt; u32 {
        xp_to_next_level(self.level)
    }

    pub fn click_damage(&amp;self) -&gt; u32 {
        click_damage(self.base_damage(), self.bonus_damage())
    }

    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }

    fn bonus_damage(&amp;self) -&gt; u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }

    pub(crate) fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold &lt; cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::NotEnoughGold);
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased);
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }
}
</code></pre>
</details>

<details>
<summary><code>src/game.rs</code> · 158 lines</summary>

<pre><code class="language-rust">use crate::enemy::Enemy;
use crate::player::{Player, PurchaseOutcome};
use crate::rules::{health_after_hit, idle_damage_for_level, is_defeated};

pub struct Game {
    pub stage: u32,
    pub player: Player,
    pub enemy: Enemy,
}

impl Game {
    pub fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    pub fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);
        self.resolve_hit();
    }

    pub fn tick(&amp;mut self, seconds: u32) {
        for _ in 0..seconds {
            let damage = idle_damage_for_level(self.player.level);

            self.enemy.health = health_after_hit(self.enemy.health, damage);
            self.resolve_hit();
        }
    }

    fn resolve_hit(&amp;mut self) {
        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }

    pub fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
}

impl Default for Game {
    fn default() -&gt; Game {
        Game::new()
    }
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    enemy.health = health_after_hit(enemy.health, player.click_damage());
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 5);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 15);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }

    #[test]
    fn one_second_is_one_idle_hit() {
        let mut game = Game::new();

        game.tick(1);

        assert_eq!(game.enemy.health, 19);
    }

    #[test]
    fn zero_seconds_changes_nothing() {
        let mut game = Game::new();

        game.tick(0);

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
    }

    #[test]
    fn enough_time_defeats_the_enemy() {
        let mut game = Game::new();

        game.tick(20);

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }

    #[test]
    fn one_long_tick_matches_many_short_ones() {
        let mut long = Game::new();
        let mut short = Game::new();

        long.tick(45);

        for _ in 0..45 {
            short.tick(1);
        }

        assert_eq!(long.stage, short.stage);
        assert_eq!(long.enemy.health, short.enemy.health);
        assert_eq!(long.player.gold, short.player.gold);
        assert_eq!(long.player.xp, short.player.xp);
        assert_eq!(long.player.level, short.player.level);
    }
}
</code></pre>
</details>

<details>
<summary><code>tests/idle_hour.rs</code> · 11 lines</summary>

<pre><code class="language-rust">use rustbound_idle::Game;

#[test]
fn an_hour_away_moves_the_game_forward() {
    let mut game = Game::new();

    game.tick(3600);

    assert_eq!(game.stage, 128);
    assert_eq!(game.player.level, 47);
}
</code></pre>
</details>

<p>Run <code>cargo test</code>, and twenty-two tests pass: twenty-one inside the library and one in <code>tests/idle_hour.rs</code>. Run <code>cargo run</code>, and the player clears three stages, buys an upgrade, and spends an hour away.</p>
<p>Hold on to twenty-two. Today we add six.</p>
<hr />
<h2>A save is just text</h2>
<p>What is a save, exactly?</p>
<p>Strip away the menus and the little floppy-disk icon, and a save is a description of the game, written down somewhere that outlives the program.</p>
<p>That icon was a real object once. Our house had hundreds of them, the 5.25-inch kind that actually flopped, full of games and programs. They are probably still boxed up somewhere at my parents' house. The idea outlived them.</p>
<p>For our game, "the game" is one <code>Game</code> value: a stage, a player, and the slime they are fighting. Write those numbers down and you have a save. Read them back into a <code>Game</code> and you have loaded it.</p>
<p>So the question for today is: write them down <em>how</em>?</p>
<p>We could invent a format. One number per line, in a fixed order, and a parser that reads them back:</p>
<pre><code class="language-text">4
11
4
2
1
50
</code></pre>
<p>It would work, until the day we add a field in the middle and every old save quietly reads the wrong number into the wrong slot. Nothing crashes. The player just loads in with fifty gold and an upgrade level of eleven.</p>
<p>There is a better answer, and most programs that save anything already use it.</p>
<p>JSON is a plain-text format for describing values with names. Here is a brand-new game, written as JSON:</p>
<pre><code class="language-json">{
  "save_version": 1,
  "stage": 1,
  "player": {
    "gold": 0,
    "xp": 0,
    "level": 1,
    "click_upgrade_level": 0
  },
  "enemy_health": 20
}
</code></pre>
<p>Every value has a name, and values can nest: <code>player</code> is a group of four values inside the bigger one.</p>
<p>Notice that this is <strong>not</strong> a copy of every field in <code>Game</code>. There is no enemy name and no reward. It is the minimum we need to rebuild the same game: the stage, the player's progress, and how much health the current slime has left.</p>
<p>That last one matters more than it looks. Growing up, my brothers and I often played Super Nintendo games on an emulator instead of the console, and a big reason was being able to save anywhere. <code>enemy_health</code> is our version of that. We can save in the middle of a fight, and the slime will still be wounded when we come back.</p>
<p>The <code>save_version</code> line looks like it does nothing yet. It is there for a future version of us, and we will come back to it.</p>
<p><strong>Question:</strong> Why save the game as named JSON values instead of one number per line?</p>
<ol>
<li>JSON files are always smaller</li>
<li>Every value carries its name, so a new or reordered field cannot slide into the wrong slot</li>
<li>Rust cannot read a file of plain numbers</li>
</ol>
<p><strong>Answer:</strong> 2. Every value carries its name, so a new or reordered field cannot slide into the wrong slot</p>
<blockquote>
<p><strong>Explanation:</strong> A fixed order is a promise that nothing will ever change. Names let the loader find <code>gold</code> wherever it is.</p>
</blockquote>
<p>We are not going to write the JSON writer and reader by hand. Someone already has, and it is very good.</p>
<hr />
<h2>The first dependency</h2>
<p>Until now, every line of this game has been ours. Today we borrow some.</p>
<p>Rust code that other people share is published as crates, and most of them live on <a href="https://crates.io">crates.io</a>. A crate your project uses is called a <strong>dependency</strong>. We are adding two:</p>
<ul>
<li><code>serde</code> knows how to take a Rust value apart into named pieces, and put it back together. The name is short for <em>serialize</em> and <em>deserialize</em>, which are the formal words for writing a value down and reading it back.</li>
<li><code>serde_json</code> knows how to write those pieces as JSON, and read JSON back into pieces.</li>
</ul>
<p>Open <code>Cargo.toml</code>. The <code>[dependencies]</code> section has been empty since the first article. Fill it in:</p>
<p><strong>Cargo.toml</strong></p>
<pre><code class="language-toml">[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
</code></pre>
<p><code>"1"</code> means <em>any version 1.x</em>. Cargo picks the newest 1.x it can find, and never jumps to a 2.0 on its own.</p>
<p>The <code>features = ["derive"]</code> part matters, and we will see why in a moment.</p>
<p>Build it:</p>
<pre><code class="language-bash">cargo build
</code></pre>
<p>The first time, Cargo downloads the two crates and the handful of smaller ones they depend on, and compiles all of them. You will see a dozen <code>Compiling</code> lines you did not write, and then your own:</p>
<pre><code class="language-text">   Compiling serde v1.0.229
   Compiling serde_json v1.0.154
   Compiling serde_derive v1.0.229
   ...
   Compiling rustbound-idle v0.1.0
</code></pre>
<p>Your version numbers may be newer. That is fine.</p>
<p>That only happens once. Cargo keeps the compiled crates around, and later builds are as fast as before.</p>
<p>Look in your project folder and you will find a new file, <code>Cargo.lock</code>. Cargo wrote it. It records the exact versions that got picked, so that anyone who builds this project later gets the same ones. Leave it alone, and if you use Git, commit it.</p>
<hr />
<h2>Give the save its own shape</h2>
<p><code>serde</code> can turn Rust values into text and back again, but there is a choice to make first.</p>
<p>We <em>could</em> teach <code>serde</code> about <code>Game</code>, <code>Player</code>, and <code>Enemy</code> directly.</p>
<p>That would work.</p>
<p>It would also make our runtime structs the permanent shape of our save files.</p>
<p>Those are not the same job.</p>
<p>A <code>Game</code> is whatever the engine needs while it is running.</p>
<p>A save is the minimum information needed to rebuild that game later.</p>
<p>The difference matters because some values can be recalculated from the rules. An enemy's gold and XP rewards come from its stage. If we saved those numbers, a save made today would carry today's rewards into next month's version of the game, even after we rebalanced them.</p>
<p>So the save gets its own small types.</p>
<p>Create <code>src/save.rs</code>:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">use serde::{Deserialize, Serialize};

const SAVE_VERSION: u32 = 1;

#[derive(Serialize, Deserialize)]
struct PlayerSave {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}

#[derive(Serialize, Deserialize)]
struct SaveData {
    save_version: u32,
    stage: u32,
    player: PlayerSave,
    enemy_health: u32,
}
</code></pre>
<p>The two structs are the JSON from before, written as Rust. Same names, same nesting.</p>
<p><code>const</code> declares a <strong>constant</strong>: a value fixed when the program is compiled, which can never change while it runs. Constants are written in capitals by convention, and they need their type spelled out. <code>SAVE_VERSION</code> is the version of the save format this code writes.</p>
<p>The interesting line is the one above each struct.</p>
<h3>Asking for a trait</h3>
<p>In plain terms, that line tells Rust: <em>these two structs can be written down, and read back.</em></p>
<p><code>#[derive(Serialize, Deserialize)]</code> is the same kind of line as the <code>#[derive(Debug, PartialEq)]</code> we put on <code>PurchaseOutcome</code>.</p>
<p><code>Serialize</code> and <code>Deserialize</code> are <strong>traits</strong>. A trait is a named ability a type can have. <code>PartialEq</code> is the ability to be compared with <code>==</code>. <code>Serialize</code> is the ability to be taken apart into named pieces. <code>Deserialize</code> is the ability to be built back up from them.</p>
<p><code>derive</code> asks the compiler to write that ability for us, by looking at the fields. We could write it by hand. Nobody wants to. The thing that does the writing is called a <strong>derive macro</strong>; a macro is code that writes code.</p>
<p>That is also what <code>features = ["derive"]</code> was for. Without it, <code>serde</code> gives us the traits but not the code that derives them, and the build stops on every struct:</p>
<pre><code class="language-text">error: cannot find derive macro `Serialize` in this scope
  --&gt; src\save.rs:5:10
   |
 5 | #[derive(Serialize, Deserialize)]
   |          ^^^^^^^^^
   |
note: `Serialize` is imported here, but it is only a trait, without a derive macro
</code></pre>
<p><em>Only a trait, without a derive macro.</em> The ability exists; the machine that writes it for us was left out.</p>
<p>Last, the library has to know the new file exists. Add <code>mod save;</code> to <code>src/lib.rs</code>:</p>
<p><strong>src/lib.rs</strong></p>
<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;
mod save;

pub use enemy::Enemy;
pub use game::Game;
pub use player::{Player, PurchaseOutcome};
</code></pre>
<p>If you build now, Cargo warns that <code>SAVE_VERSION</code>, <code>PlayerSave</code>, and <code>SaveData</code> are never used. Fair. The next section uses them.</p>
<p><strong>Question:</strong> Why does the save store the stage but not the enemy's <code>gold_reward</code>?</p>
<ol>
<li><code>gold_reward</code> is <code>pub(crate)</code>, so <code>serde</code> cannot see it</li>
<li>JSON cannot hold more than six numbers</li>
<li>The reward can be rebuilt from the stage, so an old save cannot carry old balance rules into a newer game</li>
</ol>
<p><strong>Answer:</strong> 3. The reward can be rebuilt from the stage, so an old save cannot carry old balance rules into a newer game</p>
<blockquote>
<p><strong>Explanation:</strong> Save what only the player could have done. Rebuild everything else from the current rules.</p>
</blockquote>
<hr />
<h2>Writing it down</h2>
<p>Saving is the easy direction. Every <code>Game</code> can be described, so <code>save_json()</code> always gets an answer.</p>
<p>Add an <code>impl Game</code> block to <code>src/save.rs</code>, below the structs. It is a second <code>impl</code> block for <code>Game</code>, in a different file from the first, and Rust is fine with that: an impl block can live anywhere in the crate that owns the type.</p>
<p>First, one more import at the top of the file, beside the <code>serde</code> one:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">use crate::game::Game;
</code></pre>
<p>Then the block itself:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">impl Game {
    pub fn save_json(&amp;self) -&gt; String {
        let save = SaveData {
            save_version: SAVE_VERSION,
            stage: self.stage,
            player: PlayerSave {
                gold: self.player.gold,
                xp: self.player.xp,
                level: self.player.level,
                click_upgrade_level: self.player.click_upgrade_level,
            },
            enemy_health: self.enemy.health,
        };

        serde_json::to_string(&amp;save).expect("a save made of numbers should always serialize")
    }
}
</code></pre>
<p>The method copies the numbers that matter out of the running game into a <code>SaveData</code>, and hands it to <code>serde_json::to_string</code>.</p>
<p>That last line has a word we have not seen.</p>
<p><code>serde_json::to_string</code> does not hand back a <code>String</code>. It hands back a <strong><code>Result</code></strong>: either <code>Ok</code>, holding the text, or <code>Err</code>, holding what went wrong. It has to, because some Rust values cannot be written as JSON at all.</p>
<p>Ours can. A struct full of numbers always can. So we call <code>.expect()</code>, which says:</p>
<blockquote>
<p>Give me what is inside the <code>Ok</code>. If it is somehow an <code>Err</code>, stop the program with this message.</p>
</blockquote>
<p><code>expect()</code> is for the cases where failure would mean a bug in our own code, not bad luck. We will meet a case that is pure bad luck in a moment, and there we will not use it.</p>
<p>Now a test that pins the format down. Add it to the bottom of <code>src/save.rs</code>:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_new_game_saves_its_starting_numbers() {
        let json = Game::new().save_json();

        assert_eq!(
            json,
            r#"{"save_version":1,"stage":1,"player":{"gold":0,"xp":0,"level":1,"click_upgrade_level":0},"enemy_health":20}"#
        );
    }
}
</code></pre>
<p>That string starts with <code>r#"</code> and ends with <code>"#</code>. It is a <strong>raw string</strong>. JSON is full of double quotes, and in a normal string every one of them would need a backslash in front of it. In a raw string, a <code>"</code> is just a character, and only <code>"#</code> ends it.</p>
<p><code>serde_json::to_string</code> writes everything on one line with no spaces, which is why the expected text is one long line.</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>Twenty-three tests pass.</p>
<hr />
<h2>Reading it back</h2>
<p>Loading is the hard direction.</p>
<p>Saving starts from a <code>Game</code>, which is always valid. Loading starts from text, and text can be anything. A save from an older version. A half-written file. Somebody's grocery list.</p>
<p>So what should a load function return when the text is not a save?</p>
<p>A <code>Game</code>? Which one? A brand-new game would quietly wipe out the player's progress, and nothing would tell them why. It is the same trap as the shop that answered <code>false</code> in an earlier lesson: an answer that looks like an answer, where a wrong guess about what it means looks just like success.</p>
<p>The honest return type is the one <code>serde_json</code> already used on us:</p>
<pre><code class="language-rust">Result&lt;Game, LoadError&gt;
</code></pre>
<blockquote>
<p>Either a <code>Game</code>, or the reason there is no <code>Game</code>.</p>
</blockquote>
<p><code>LoadError</code> is ours to define. It is an enum, like <code>PurchaseOutcome</code>, listing the ways loading can fail. For now there is one. Add it near the top of <code>src/save.rs</code>, below <code>SAVE_VERSION</code>:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">#[derive(Debug, PartialEq)]
pub enum LoadError {
    Unreadable,
}
</code></pre>
<p>Then the load itself, inside the same <code>impl Game</code> block, below <code>save_json()</code>:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">    pub fn from_saved_json(json: &amp;str) -&gt; Result&lt;Game, LoadError&gt; {
        let save: SaveData = match serde_json::from_str(json) {
            Ok(save) =&gt; save,
            Err(_) =&gt; return Err(LoadError::Unreadable),
        };

        let mut game = Game::new();

        game.stage = save.stage;
        game.player.gold = save.player.gold;
        game.player.xp = save.player.xp;
        game.player.level = save.player.level;
        game.player.click_upgrade_level = save.player.click_upgrade_level;

        game.enemy = Enemy::for_stage(save.stage);
        game.enemy.health = save.enemy_health;

        Ok(game)
    }
</code></pre>
<p>And one more import at the top, because we build an enemy:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">use crate::enemy::Enemy;
</code></pre>
<p>Let us walk through it.</p>
<p><code>json: &amp;str</code> means the function borrows some text instead of taking it. <code>&amp;str</code> is a borrowed piece of text, the way <code>&amp;Player</code> is a borrowed player. Whoever calls <code>from_saved_json()</code> keeps their <code>String</code>.</p>
<p><code>serde_json::from_str(json)</code> reads the text. It returns a <code>Result</code> too, and this time failure is real: the text might be anything. So we <code>match</code> on it, exactly the way we matched on <code>PurchaseOutcome</code>, one arm per possibility:</p>
<ul>
<li><code>Ok(save) =&gt; save</code> takes the <code>SaveData</code> out of the <code>Ok</code> and makes it the value of the whole <code>match</code>.</li>
<li><code>Err(_) =&gt; return Err(LoadError::Unreadable)</code> ignores the details of what went wrong (that is the <code>_</code>) and leaves the function early with our own error.</li>
</ul>
<p>The <code>: SaveData</code> after <code>let save</code> matters more than it looks. <code>from_str</code> can build anything that implements <code>Deserialize</code>. That annotation is how it knows to build a <code>SaveData</code>, and therefore which names to look for.</p>
<p>The rest rebuilds the game. Start from a new <code>Game</code>, copy the saved progress over it, then ask <code>Enemy::for_stage()</code> for the enemy. That one line is where the save's small shape pays off. The rewards come from the current rules, not from the file. Only the slime's remaining health comes from the save.</p>
<p>Last, <code>Ok(game)</code> wraps the finished game in the success half of the <code>Result</code>.</p>
<p>Here is the same round trip on its own. It runs in the page, using the same two crates.</p>
<p>Run it, and you should see the player saved and loaded. Then break the save on purpose: delete <code>"level":2,</code> from the <code>text</code> line and run again. Then put it back and change <code>11</code> to <code>-5</code>.</p>
<p><strong>Live example · break the save on purpose</strong></p>
<pre><code class="language-rust">use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct PlayerSave {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}

fn main() {
    let player = PlayerSave {
        gold: 11,
        xp: 4,
        level: 2,
        click_upgrade_level: 1,
    };

    let json = serde_json::to_string(&amp;player)
        .expect("a save made of numbers should always serialize");
    println!("Saved: {json}");

    let text = r#"{"gold":11,"xp":4,"level":2,"click_upgrade_level":1}"#;
    let loaded: Result&lt;PlayerSave, serde_json::Error&gt; = serde_json::from_str(text);

    match loaded {
        Ok(player) =&gt; println!("Loaded: level {} with {} gold", player.level, player.gold),
        Err(error) =&gt; println!("Not a save: {error}"),
    }
}
</code></pre>
<p><a href="https://mnixblog.netlify.app/rust-save-load-serde-result/">Run and edit this example on the canonical article</a>.</p>
<p>The first break prints:</p>
<pre><code class="language-text">Not a save: missing field `level` at line 1 column 42
</code></pre>
<p>The second:</p>
<pre><code class="language-text">Not a save: invalid value: integer `-5`, expected u32 at line 1 column 10
</code></pre>
<p><code>serde_json</code> knew exactly what was wrong both times. <code>from_saved_json()</code> throws those details away and says <code>Unreadable</code>, because the game cannot do anything more useful with them. Either way, the <code>Err</code> arm prints and nothing crashes.</p>
<p><strong>Question:</strong> Why does <code>from_saved_json()</code> return <code>Result&lt;Game, LoadError&gt;</code> instead of a <code>Game</code>?</p>
<ol>
<li>A <code>Result</code> loads faster than a plain struct</li>
<li>Text that is not a save has no honest <code>Game</code> to return, and the caller has to handle both cases</li>
<li><code>serde_json</code> only works inside functions that return a <code>Result</code></li>
</ol>
<p><strong>Answer:</strong> 2. Text that is not a save has no honest <code>Game</code> to return, and the caller has to handle both cases</p>
<blockquote>
<p><strong>Explanation:</strong> Returning <code>Game::new()</code> on failure would look like success and quietly wipe the player's progress.</p>
</blockquote>
<hr />
<h2>Rust will not let us forget</h2>
<p>Time to use it. In <code>src/main.rs</code>, after the <code>while</code> loop and before the hour away, save the game:</p>
<p><strong>src/main.rs</strong></p>
<pre><code class="language-rust">    let save = game.save_json();
    println!("Saved: {save}");
</code></pre>
<p>Then load it back and print where we are. The tempting version is two lines, right under the save lines:</p>
<pre><code class="language-rust">    let mut game = Game::from_saved_json(&amp;save);
    println!("Loaded. Stage {}, level {}.", game.stage, game.player.level);
</code></pre>
<p>Build that, and Rust refuses, several times over:</p>
<pre><code class="language-text">error[E0609]: no field `stage` on type `Result&lt;Game, rustbound_idle::save::LoadError&gt;`
  --&gt; src\main.rs:49:50
   |
49 |     println!("Loaded. Stage {}, level {}.", game.stage, game.player.level);
   |                                                  ^^^^^ unknown field
</code></pre>
<p>The other errors say the same thing about <code>player</code>, and about every later line that touches <code>game</code>.</p>
<p><code>game</code> is not a <code>Game</code>. It is a <code>Result</code>, and a <code>Result</code> has no stage. The <code>Game</code> is inside it, maybe.</p>
<p>That is the whole point. Other languages often hand back the game <em>or</em> a special empty value and trust you to check. Rust hands back a box with one of two things in it, and will not let you reach for the game until you have said what happens if it is the other thing.</p>
<p>So we say it, with a <code>match</code>. Replace the two tempting lines with:</p>
<p><strong>src/main.rs</strong></p>
<pre><code class="language-rust">    let mut game = match Game::from_saved_json(&amp;save) {
        Ok(game) =&gt; game,
        Err(error) =&gt; {
            println!("Could not load the save: {error:?}");
            return;
        }
    };

    println!("Loaded. Stage {}, level {}.", game.stage, game.player.level);
</code></pre>
<p>If the load worked, the <code>Game</code> comes out of the <code>Ok</code>. If not, we print the error and <code>return</code> from <code>main()</code>, which ends the program. <code>{error:?}</code> prints the error with <code>Debug</code>, which is why <code>LoadError</code> derives it.</p>
<p>Anyone outside the library who wants to <code>match</code> on the different kinds of <code>LoadError</code> has to be able to name it. So it goes on the front door, in <code>src/lib.rs</code>:</p>
<p><strong>src/lib.rs</strong></p>
<pre><code class="language-rust">pub use save::LoadError;
</code></pre>
<p><code>main.rs</code> does not name it yet. The browser version of the game, a couple of lessons from now, will.</p>
<hr />
<h2>Throw the game away</h2>
<p>Look at the code we just wrote again. There is something odd about it.</p>
<p>We already had a <code>game</code>. Then we wrote <code>let mut game = ...</code> a second time.</p>
<p>That is allowed. A new <code>let</code> with an old name makes a brand-new variable, and the old one can no longer be reached by that name. Rust calls this <strong>shadowing</strong>.</p>
<p>But the old game is still there, sitting in memory until <code>main()</code> ends. And we want to <em>prove</em> the load works, not just print a game that might secretly be the old one.</p>
<p>So we throw the old one away first, on purpose, right after saving:</p>
<p><strong>src/main.rs</strong></p>
<pre><code class="language-rust">    let save = game.save_json();
    println!("Saved: {save}");

    drop(game);
</code></pre>
<p><code>drop()</code> takes a value and destroys it.</p>
<p><em>Takes</em> is the important word. In the borrowing article, passing a <code>Player</code> into a function <strong>moved</strong> it: the function owned it now, and the old variable was empty. <code>drop(game)</code> moves our game into <code>drop</code>, and <code>drop</code> simply ends it.</p>
<p>Try to use the old game anyway. Put this on the line right after <code>drop(game);</code>:</p>
<pre><code class="language-rust">    println!("{}", game.stage);
</code></pre>
<p>Rust stops you:</p>
<pre><code class="language-text">error[E0382]: borrow of moved value: `game`
  --&gt; src\main.rs:49:20
   |
 4 |     let mut game = Game::new();
   |         -------- move occurs because `game` has type `Game`, which does not implement the `Copy` trait
...
48 |     drop(game);
   |          ---- value moved here
49 |     println!("{}", game.stage);
   |                    ^^^^^^^^^^ value borrowed here after move
</code></pre>
<p>Delete that line again. <em>Value moved here.</em> After <code>drop(game)</code>, the only things left of the game are the text in <code>save</code> and whatever we rebuild from it.</p>
<p>So when the program prints <code>Loaded. Stage 4, level 2.</code> and then spends an hour, every number it shows came through that line of JSON.</p>
<p>Try it on a smaller game. Run it, and you should see <code>Restored game: stage 4</code>. Then remove the <code>//</code> in front of the <code>println!</code> that reads the old game and run it again. Put it back when you have read the error.</p>
<p><strong>Live example · use it after it is gone</strong></p>
<pre><code class="language-rust">struct Game {
    stage: u32,
}

fn main() {
    let game = Game { stage: 4 };
    let saved_stage = game.stage;

    drop(game);

    // println!("Old game: stage {}", game.stage);

    let game = Game { stage: saved_stage };

    println!("Restored game: stage {}", game.stage);
}
</code></pre>
<p><a href="https://mnixblog.netlify.app/rust-save-load-serde-result/">Run and edit this example on the canonical article</a>.</p>
<p>Back in your project, run the real game:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<pre><code class="language-text">Stage 1 cleared!  Gold: 5  XP: 6/20
  Next up: Training Slime with 30 health.
Stage 2 cleared!  Gold: 12  XP: 14/20
  Next up: Training Slime with 40 health.
Bought click upgrade 1! Click damage: 7  Gold left: 2
Stage 3 cleared!  Gold: 11  XP: 4/35
Level up! Now level 2. Click damage: 8
  Next up: Training Slime with 50 health.
Saved: {"save_version":1,"stage":4,"player":{"gold":11,"xp":4,"level":2,"click_upgrade_level":1},"enemy_health":50}
Loaded. Stage 4, level 2.
One hour away.
Stage 4 -&gt; 131  Level 2 -&gt; 48  Gold 11 -&gt; 17410
</code></pre>
<p>The last line is the same one the game printed last time, with no save involved. The game that spent the hour was not the game that cleared the stages. It only remembered being it.</p>
<hr />
<h2>A save from the future</h2>
<p>Now that <code>save_version</code> field.</p>
<p>Some day the save format will change. Maybe a second upgrade, maybe a pet. When it does, we bump <code>SAVE_VERSION</code> to 2.</p>
<p>Then someone opens the new version of the game with a save from version 1. Or, worse, opens an old copy of the game with a save from version 2. Their save might even read correctly as JSON, and still mean something different from what this code thinks it means.</p>
<p>We cannot know today how to convert between formats that do not exist yet. What we can do today is notice the mismatch and refuse, instead of guessing.</p>
<p>That is a second way loading can fail. And unlike <code>Unreadable</code>, this one has a detail worth keeping: <em>which</em> version the save claimed to be.</p>
<p><strong>Coding challenge: Refuse a save from the future</strong></p>
<p>Below is a smaller loader that only reads a version and a stage. It already turns unreadable text into <code>LoadError::Unreadable</code>. Where the <code>// TODO</code> is, add the check that refuses any <code>save_version</code> that is not <code>SAVE_VERSION</code>, with <code>LoadError::UnsupportedVersion</code> holding the version the save claimed.</p>
<p>Keep the names <code>load_stage</code>, <code>LoadError</code>, <code>Unreadable</code>, <code>UnsupportedVersion</code>, and <code>SAVE_VERSION</code>, because the checks use them. <strong>Check my code</strong> runs hidden tests against your version.</p>
<p><strong>Expected result:</strong> the starter prints <code>Ok(4)</code>, <code>Ok(4)</code>, <code>Err(Unreadable)</code>. With your check, the middle line becomes <code>Err(UnsupportedVersion(99))</code>. Until then you will also see two "never used" warnings, for <code>SAVE_VERSION</code> and <code>UnsupportedVersion</code>. They are a hint: your check is what uses them.</p>
<p><strong>Refuse a save from the future</strong></p>
<pre><code class="language-rust">use serde::Deserialize;

const SAVE_VERSION: u32 = 1;

#[derive(Debug, PartialEq)]
enum LoadError {
    Unreadable,
    UnsupportedVersion(u32),
}

#[derive(Deserialize)]
struct SaveData {
    save_version: u32,
    stage: u32,
}

fn load_stage(json: &amp;str) -&gt; Result&lt;u32, LoadError&gt; {
    let save: SaveData = match serde_json::from_str(json) {
        Ok(save) =&gt; save,
        Err(_) =&gt; return Err(LoadError::Unreadable),
    };

    // TODO: refuse any save_version that is not SAVE_VERSION

    Ok(save.stage)
}

fn main() {
    println!("{:?}", load_stage(r#"{"save_version":1,"stage":4}"#));
    println!("{:?}", load_stage(r#"{"save_version":99,"stage":4}"#));
    println!("{:?}", load_stage("not a save"));
}
</code></pre>
<details>
<summary>Spoiler: the checks for this challenge</summary>

<p>These tests run when you press Check my code on the original article. Reading them first gives away the answer.</p>
<pre><code class="language-rust">/// A save with the current version should still load and give back its stage.
#[test]
fn the_current_version_loads() {
    assert_eq!(load_stage(r#"{"save_version":1,"stage":4}"#), Ok(4));
}

/// A save from version 99 should be refused with `LoadError::UnsupportedVersion`, carrying the 99 so the caller knows which version it was.
#[test]
fn another_version_is_refused() {
    assert_eq!(
        load_stage(r#"{"save_version":99,"stage":4}"#),
        Err(LoadError::UnsupportedVersion(99))
    );
}

/// Text that is not a save at all should still be `LoadError::Unreadable`. Check the version only after the text has been read.
#[test]
fn nonsense_is_still_unreadable() {
    assert_eq!(load_stage("not a save"), Err(LoadError::Unreadable));
}

/// `main()` should run to the end without panicking.
#[test]
fn main_runs() {
    main();
}
</code></pre>
</details>

<p><a href="https://mnixblog.netlify.app/rust-save-load-serde-result/">Run and edit this example on the canonical article</a>.</p>
<blockquote>
<p><strong>Tip:</strong>
It is an <code>if</code> with an early <code>return</code>, the same shape as the "not enough gold" check in <code>buy_click_upgrade()</code>: compare <code>save.save_version</code> with <code>SAVE_VERSION</code>, and return <code>Err(LoadError::UnsupportedVersion(save.save_version))</code> when they differ.</p>
</blockquote>
<p>Here is the same change in the real game. First the new variant:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">#[derive(Debug, PartialEq)]
pub enum LoadError {
    Unreadable,
    UnsupportedVersion(u32),
}
</code></pre>
<p><code>UnsupportedVersion(u32)</code> is an enum variant that <strong>carries a value</strong>. <code>PurchaseOutcome::Purchased</code> was just a name. <code>LoadError::UnsupportedVersion(99)</code> is a name <em>and</em> a number: the version that got refused. A <code>match</code> can pull that number back out, the same way <code>Ok(save)</code> pulled out the save.</p>
<p>Then the check, in <code>from_saved_json()</code>, right after the <code>match</code> and before the game is rebuilt:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">        if save.save_version != SAVE_VERSION {
            return Err(LoadError::UnsupportedVersion(save.save_version));
        }
</code></pre>
<p>There is nothing to migrate yet. Version 1 is the only version that has ever existed. But every save written from today on says which version wrote it, and that is the part we could never add later. A save without a version number stays without one forever.</p>
<hr />
<h2>Tests that say no</h2>
<p>A broken save should get a polite no, not a wiped game. These tests prove it does.</p>
<p>The loader has three ways out: a game, <code>Unreadable</code>, and <code>UnsupportedVersion</code>. Each one deserves a test.</p>
<p>The obvious way to write the first one does not compile:</p>
<pre><code class="language-rust">assert_eq!(
    Game::from_saved_json("definitely not a save"),
    Err(LoadError::Unreadable)
);
</code></pre>
<pre><code class="language-text">error[E0369]: binary operation `==` cannot be applied to type `Result&lt;game::Game, save::LoadError&gt;`
note: an implementation of `PartialEq` might be missing for `game::Game`

error[E0277]: `game::Game` doesn't implement `Debug`
</code></pre>
<p><code>assert_eq!</code> compares the two sides with <code>==</code>, and prints both if they differ. A <code>Result&lt;Game, LoadError&gt;</code> can only do that if <code>Game</code> can, and <code>Game</code> has never needed to be compared or printed. We could derive <code>PartialEq</code> and <code>Debug</code> on <code>Game</code>, <code>Player</code>, and <code>Enemy</code> just for this. But we do not want to compare whole games here. We want to ask one question: <em>is this an <code>Err(LoadError::Unreadable)</code>?</em></p>
<p>That is what <code>matches!</code> does. The <code>!</code> means it is a macro, like <code>println!</code> and <code>assert_eq!</code>. It takes a value and a pattern, the same kind of pattern a <code>match</code> arm uses, and gives back <code>true</code> if the value fits:</p>
<pre><code class="language-rust">assert!(matches!(
    Game::from_saved_json("definitely not a save"),
    Err(LoadError::Unreadable)
));
</code></pre>
<p>Here are all the new unit tests. They go inside the <code>tests</code> module at the bottom of <code>src/save.rs</code>, after <code>a_new_game_saves_its_starting_numbers</code>:</p>
<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">    #[test]
    fn a_loaded_game_rebuilds_the_enemy_from_the_rules() {
        let mut game = Game::new();
        game.tick(600);

        let restored = Game::from_saved_json(&amp;game.save_json()).expect("the save should load");

        assert_eq!(restored.stage, game.stage);
        assert_eq!(restored.enemy.health, game.enemy.health);
        assert_eq!(restored.enemy.gold_reward, game.enemy.gold_reward);
        assert_eq!(restored.enemy.xp_reward, game.enemy.xp_reward);
    }

    #[test]
    fn nonsense_is_not_a_save() {
        assert!(matches!(
            Game::from_saved_json("definitely not a save"),
            Err(LoadError::Unreadable)
        ));
    }

    #[test]
    fn a_save_with_missing_pieces_is_not_a_save() {
        let json = r#"{"save_version":1,"stage":4}"#;

        assert!(matches!(
            Game::from_saved_json(json),
            Err(LoadError::Unreadable)
        ));
    }

    #[test]
    fn a_save_from_another_version_is_refused() {
        let json = r#"{
            "save_version": 99,
            "stage": 1,
            "player": { "gold": 0, "xp": 0, "level": 1, "click_upgrade_level": 0 },
            "enemy_health": 20
        }"#;

        assert!(matches!(
            Game::from_saved_json(json),
            Err(LoadError::UnsupportedVersion(99))
        ));
    }
</code></pre>
<p>The first one checks the rewards, which are not in the save at all. It can read <code>gold_reward</code> because it lives inside the library, where <code>pub(crate)</code> fields are visible. Here <code>expect()</code> is the right tool: if a save we just made does not load, that is a bug, and the test should fail loudly.</p>
<p>The last one shows that JSON does not care about spaces or line breaks. The loader reads that save fine. It refuses it anyway, because of the 99.</p>
<p><strong>Question:</strong> Why do the failure tests use <code>assert!(matches!(...))</code> instead of <code>assert_eq!</code>?</p>
<ol>
<li><code>matches!</code> runs faster than <code>assert_eq!</code></li>
<li><code>assert_eq!</code> needs both sides to support <code>==</code> and <code>Debug</code>, and <code>Game</code> supports neither</li>
<li><code>assert_eq!</code> cannot compare enum variants that carry a value</li>
</ol>
<p><strong>Answer:</strong> 2. <code>assert_eq!</code> needs both sides to support <code>==</code> and <code>Debug</code>, and <code>Game</code> supports neither</p>
<blockquote>
<p><strong>Explanation:</strong> <code>matches!</code> only asks whether a value fits a pattern, so <code>Game</code> never has to be compared or printed.</p>
</blockquote>
<p>One more test, from the outside, the same way <code>tests/idle_hour.rs</code> tests the library. Create <code>tests/save_and_restore.rs</code>:</p>
<p><strong>tests/save_and_restore.rs</strong></p>
<pre><code class="language-rust">use rustbound_idle::Game;

#[test]
fn a_restored_game_plays_the_same_hour() {
    let mut original = Game::new();
    original.tick(600);

    let mut restored = Game::from_saved_json(&amp;original.save_json()).expect("the save should load");

    original.tick(3600);
    restored.tick(3600);

    assert_eq!(restored.stage, original.stage);
    assert_eq!(restored.enemy.health, original.enemy.health);
    assert_eq!(restored.player.gold, original.player.gold);
    assert_eq!(restored.player.xp, original.player.xp);
    assert_eq!(restored.player.level, original.player.level);
}
</code></pre>
<p>It is a cousin of <code>one_long_tick_matches_many_short_ones</code>. Two games that should be the same, run side by side, compared at the end. If the save left anything out, an hour is plenty of time for the difference to show.</p>
<p>Run everything:</p>
<pre><code class="language-bash">cargo test
cargo clippy
</code></pre>
<p>Twenty-eight tests pass: twenty-six inside the library, and one in each of the two files in <code>tests/</code>. Clippy should be quiet.</p>
<hr />
<h2>The new and changed files</h2>
<p>If anything above went sideways, here is every file that changed. The rest are exactly as they were in "Our starting point."</p>
<p><code>Cargo.toml</code> (only the <code>[dependencies]</code> section changed):</p>
<p><strong>Cargo.toml</strong></p>
<pre><code class="language-toml">[package]
name = "rustbound-idle"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
</code></pre>
<p><code>src/lib.rs</code>:</p>
<p><strong>src/lib.rs</strong></p>
<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;
mod save;

pub use enemy::Enemy;
pub use game::Game;
pub use player::{Player, PurchaseOutcome};
pub use save::LoadError;
</code></pre>
<details>
<summary><code>src/save.rs</code> · 130 lines</summary>

<p><strong>src/save.rs</strong></p>
<pre><code class="language-rust">use crate::enemy::Enemy;
use crate::game::Game;
use serde::{Deserialize, Serialize};

const SAVE_VERSION: u32 = 1;

#[derive(Debug, PartialEq)]
pub enum LoadError {
    Unreadable,
    UnsupportedVersion(u32),
}

#[derive(Serialize, Deserialize)]
struct PlayerSave {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}

#[derive(Serialize, Deserialize)]
struct SaveData {
    save_version: u32,
    stage: u32,
    player: PlayerSave,
    enemy_health: u32,
}

impl Game {
    pub fn save_json(&amp;self) -&gt; String {
        let save = SaveData {
            save_version: SAVE_VERSION,
            stage: self.stage,
            player: PlayerSave {
                gold: self.player.gold,
                xp: self.player.xp,
                level: self.player.level,
                click_upgrade_level: self.player.click_upgrade_level,
            },
            enemy_health: self.enemy.health,
        };

        serde_json::to_string(&amp;save).expect("a save made of numbers should always serialize")
    }

    pub fn from_saved_json(json: &amp;str) -&gt; Result&lt;Game, LoadError&gt; {
        let save: SaveData = match serde_json::from_str(json) {
            Ok(save) =&gt; save,
            Err(_) =&gt; return Err(LoadError::Unreadable),
        };

        if save.save_version != SAVE_VERSION {
            return Err(LoadError::UnsupportedVersion(save.save_version));
        }

        let mut game = Game::new();

        game.stage = save.stage;
        game.player.gold = save.player.gold;
        game.player.xp = save.player.xp;
        game.player.level = save.player.level;
        game.player.click_upgrade_level = save.player.click_upgrade_level;

        game.enemy = Enemy::for_stage(save.stage);
        game.enemy.health = save.enemy_health;

        Ok(game)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_new_game_saves_its_starting_numbers() {
        let json = Game::new().save_json();

        assert_eq!(
            json,
            r#"{"save_version":1,"stage":1,"player":{"gold":0,"xp":0,"level":1,"click_upgrade_level":0},"enemy_health":20}"#
        );
    }

    #[test]
    fn a_loaded_game_rebuilds_the_enemy_from_the_rules() {
        let mut game = Game::new();
        game.tick(600);

        let restored = Game::from_saved_json(&amp;game.save_json()).expect("the save should load");

        assert_eq!(restored.stage, game.stage);
        assert_eq!(restored.enemy.health, game.enemy.health);
        assert_eq!(restored.enemy.gold_reward, game.enemy.gold_reward);
        assert_eq!(restored.enemy.xp_reward, game.enemy.xp_reward);
    }

    #[test]
    fn nonsense_is_not_a_save() {
        assert!(matches!(
            Game::from_saved_json("definitely not a save"),
            Err(LoadError::Unreadable)
        ));
    }

    #[test]
    fn a_save_with_missing_pieces_is_not_a_save() {
        let json = r#"{"save_version":1,"stage":4}"#;

        assert!(matches!(
            Game::from_saved_json(json),
            Err(LoadError::Unreadable)
        ));
    }

    #[test]
    fn a_save_from_another_version_is_refused() {
        let json = r#"{
            "save_version": 99,
            "stage": 1,
            "player": { "gold": 0, "xp": 0, "level": 1, "click_upgrade_level": 0 },
            "enemy_health": 20
        }"#;

        assert!(matches!(
            Game::from_saved_json(json),
            Err(LoadError::UnsupportedVersion(99))
        ));
    }
}
</code></pre>
</details>

<details>
<summary><code>src/main.rs</code> · 71 lines</summary>

<p><strong>src/main.rs</strong></p>
<pre><code class="language-rust">use rustbound_idle::{Game, PurchaseOutcome};

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 3 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                game.player.xp_needed()
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    game.player.click_damage()
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }

        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                game.player.click_damage(),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold =&gt; {}
        }
    }

    let save = game.save_json();
    println!("Saved: {save}");

    drop(game);

    let mut game = match Game::from_saved_json(&amp;save) {
        Ok(game) =&gt; game,
        Err(error) =&gt; {
            println!("Could not load the save: {error:?}");
            return;
        }
    };

    println!("Loaded. Stage {}, level {}.", game.stage, game.player.level);

    let stage = game.stage;
    let level = game.player.level;
    let gold = game.player.gold;

    game.tick(3600);

    println!("One hour away.");
    println!(
        "Stage {} -&gt; {}  Level {} -&gt; {}  Gold {} -&gt; {}",
        stage, game.stage, level, game.player.level, gold, game.player.gold
    );
}
</code></pre>
</details>

<p><code>tests/save_and_restore.rs</code> is exactly as written in the section above.</p>
<hr />
<h2>What we learned</h2>
<p>Two ideas are worth keeping.</p>
<p>The first is that failure is part of the answer. <code>from_saved_json()</code> does not return a <code>Game</code>. It returns a <code>Result</code>, and anyone who calls it has to deal with both halves before they get a <code>Game</code> out. There is no way to forget. That is <code>Result</code> doing for errors what <code>PurchaseOutcome</code> did for purchases: making the outcome a value with a name, so that <code>false</code> is never the whole story.</p>
<p>The second is what the save does not contain. No damage numbers, no thresholds, no costs, and no enemy rewards. It stores the player's progress, the stage, and the enemy's remaining health. Everything else is rebuilt from the current rules.</p>
<p>Along the way, we also encountered:</p>
<ul>
<li>dependencies in <code>Cargo.toml</code>, and <code>Cargo.lock</code></li>
<li><code>serde</code> and <code>serde_json</code></li>
<li>JSON</li>
<li>traits, and deriving <code>Serialize</code> and <code>Deserialize</code></li>
<li><code>const</code></li>
<li>a save shape kept separate from the runtime <code>Game</code></li>
<li><code>Result</code>, <code>Ok</code>, and <code>Err</code></li>
<li><code>match</code> on a <code>Result</code>, and <code>expect()</code> when failure would be a bug</li>
<li><code>&amp;str</code>, and raw strings</li>
<li><code>drop()</code>, and the word <em>moved</em></li>
<li>shadowing: reusing a variable name with a new <code>let</code></li>
<li>enum variants that carry a value</li>
<li>a save version, before there is anything to migrate</li>
<li><code>matches!</code></li>
</ul>
<p>You do not need to memorize all of that yet.</p>
<p>There is still a loose end. A save is text, and anyone can edit text.</p>
<p><strong>Question:</strong> A player opens their save and changes <code>"gold":11</code> to <code>"gold":11000000</code>. What does <code>from_saved_json()</code> do today?</p>
<ol>
<li>Returns <code>Err(LoadError::Unreadable)</code>, because the number is too big</li>
<li>Loads it, because it is still valid JSON with the right version and a number that fits in a <code>u32</code></li>
<li>Refuses to compile until we add a maximum</li>
</ol>
<p><strong>Answer:</strong> 2. Loads it, because it is still valid JSON with the right version and a number that fits in a <code>u32</code></p>
<blockquote>
<p><strong>Explanation:</strong> The loader checks that the text is a save, not that the save is believable. That is a different job.</p>
</blockquote>
<p>Module privacy keeps other Rust code away from private engine details. It cannot make outside data trustworthy. Checking that a save makes sense is a different boundary, and it gets its own article later in the series. Not the next one.</p>
<hr />
<h2>Text crosses any wall</h2>
<p>Our game can now describe itself in text, and rebuild itself from text.</p>
<p>Text is one of the easiest things for different languages and environments to share. A terminal can print it. A file can hold it. A browser can keep it in storage and hand it back.</p>
<p>So far, the only thing that has ever driven this engine is <code>main()</code>, in Rust, in a terminal.</p>
<p>Next time, we compile the library to WebAssembly, a format browsers can run right beside JavaScript, and the thing on the other side of the wall is JavaScript.</p>
<p>It will not learn how combat works. It will not learn what a slime is worth. It gets a handful of operations and a save that is already text.</p>
<p>That is all it needs.</p>
]]></content:encoded></item><item><title><![CDATA[We Accidentally Built a Game Engine.]]></title><description><![CDATA[Split a Rust game into modules and a library crate, step by step, with tests passing after every move. Learn mod, pub, and pub use.]]></description><link>https://journeytocode.hashnode.dev/rust-modules-library-crate-split-game</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/rust-modules-library-crate-split-game</guid><category><![CDATA[Rust]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 25 Oct 2026 15:09:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/ed3771b6-e8ff-4671-854e-f75bc126fabb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our game works, but its rules and its screen are tangled together in one file of more than four hundred lines. Today we pull them apart, so the same game can run in a terminal now and in a browser later.</p>
<p>Nothing about the game changes. We will not touch a rule.</p>
<p>Nobody planned that file. Each article needed one more thing, and each thing went in the next free spot. But look at what is in there: everything above <code>main()</code> knows nothing about a terminal or a screen. Only <code>main()</code> does.</p>
<p>So we put a wall between them. When we are done, <code>main.rs</code> is fifty-six lines: it creates a game, clicks, and prints. The game itself lives in a library that has never heard of a terminal.</p>
<p>A library like that, made of rules and state that do not know how they are being displayed, is the heart of what people call a game engine.</p>
<p>A small one.</p>
<p>Ours will have a front door and nothing else.</p>
<p>The program still prints the same thing, a few stages cleared and then an hour of waiting:</p>
<pre><code class="language-text">One hour away.
Stage 4 -&gt; 131  Level 2 -&gt; 48  Gold 11 -&gt; 17410
</code></pre>
<p>Same output.</p>
<p>Same twenty-one tests, plus one new one.</p>
<p>Different house.</p>
<hr />
<h2>Our starting point</h2>
<p>This series builds a small idle game: a player clicks to fight slimes, earns gold and XP, and keeps progressing while you are away.</p>
<p>If you are only reading along, skip past this code to "The groups are already visible."</p>
<p>If you are building along, create a project with <code>cargo new rustbound-idle</code> if you do not already have one. The name matters later.</p>
<p>At this point, <code>src/main.rs</code> holds three structs, an enum, three <code>impl</code> blocks, twelve small functions, and twenty-one tests:</p>
<pre><code class="language-rust">struct Player {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}

struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

#[derive(Debug, PartialEq)]
enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}

impl Player {
    fn new() -&gt; Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }

    fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }

    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }

    fn bonus_damage(&amp;self) -&gt; u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }

    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold &lt; cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
}

impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);
        self.resolve_hit();
    }

    fn tick(&amp;mut self, seconds: u32) {
        for _ in 0..seconds {
            let damage = idle_damage_for_level(self.player.level);

            self.enemy.health = health_after_hit(self.enemy.health, damage);
            self.resolve_hit();
        }
    }

    fn resolve_hit(&amp;mut self) {
        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }

    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}

fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}

fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}

fn idle_damage_for_level(level: u32) -&gt; u32 {
    level
}

fn click_upgrade_bonus(upgrades: u32) -&gt; u32 {
    upgrades.saturating_mul(2)
}

fn click_upgrade_cost(upgrades: u32) -&gt; u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage());

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 3 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage())
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }

        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                click_damage(game.player.base_damage(), game.player.bonus_damage()),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold =&gt; {}
        }
    }

    let stage = game.stage;
    let level = game.player.level;
    let gold = game.player.gold;

    game.tick(3600);

    println!("One hour away.");
    println!(
        "Stage {} -&gt; {}  Level {} -&gt; {}  Gold {} -&gt; {}",
        stage, game.stage, level, game.player.level, gold, game.player.gold
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 5);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }

    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(
            player.buy_click_upgrade(),
            PurchaseOutcome::NotEnoughGold
        );
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(
            player.buy_click_upgrade(),
            PurchaseOutcome::Purchased
        );
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 15);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }

    #[test]
    fn idle_damage_grows_with_level() {
        assert_eq!(idle_damage_for_level(1), 1);
        assert_eq!(idle_damage_for_level(2), 2);
    }

    #[test]
    fn enough_time_defeats_the_enemy() {
        let mut game = Game::new();

        game.tick(20);

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }

    #[test]
    fn one_second_is_one_idle_hit() {
        let mut game = Game::new();

        game.tick(1);

        assert_eq!(game.enemy.health, 19);
    }

    #[test]
    fn zero_seconds_changes_nothing() {
        let mut game = Game::new();

        game.tick(0);

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
    }

    #[test]
    fn one_long_tick_matches_many_short_ones() {
        let mut long = Game::new();
        let mut short = Game::new();

        long.tick(45);

        for _ in 0..45 {
            short.tick(1);
        }

        assert_eq!(long.stage, short.stage);
        assert_eq!(long.enemy.health, short.enemy.health);
        assert_eq!(long.player.gold, short.player.gold);
        assert_eq!(long.player.xp, short.player.xp);
        assert_eq!(long.player.level, short.player.level);
    }
}
</code></pre>
<p>Run it, and the player clears three stages, buys an upgrade, and spends an hour away.</p>
<p>Run <code>cargo test</code>, and all twenty-one tests pass.</p>
<p>You need a recent Rust version. This project uses edition 2024, which requires Rust 1.85 or newer.</p>
<p>Hold on to that test count.</p>
<p>Everything we do today is a reorganization, and the tests are how we prove we did not break anything.</p>
<hr />
<h2>The groups are already visible</h2>
<p>Nobody has to invent categories for this code.</p>
<p>It has been sorting itself since the first article:</p>
<pre><code class="language-text">rules    click_damage, health_after_hit, is_defeated,
         enemy_health_for_stage, gold_reward_for_stage,
         xp_reward_for_stage, xp_to_next_level,
         base_damage_for_level, idle_damage_for_level,
         click_upgrade_bonus, click_upgrade_cost

enemy    Enemy
player   Player, PurchaseOutcome
game     Game, attack
main     everything that prints
</code></pre>
<p>Look at the first group.</p>
<p>Those eleven functions take plain values and return plain values.</p>
<p>They never touch a <code>Player</code>, an <code>Enemy</code>, or a <code>Game</code>.</p>
<p>They are the rules of the game, and every other group leans on them.</p>
<p>Rust has a name for a named group of code: a <strong>module</strong>.</p>
<p>For this project, we are going to give each top-level module its own file, one at a time, and run the tests after every move.</p>
<hr />
<h2>Give the rules their own module</h2>
<p>Create a new file next to <code>main.rs</code>:</p>
<pre><code class="language-text">src/
├── main.rs
└── rules.rs
</code></pre>
<p>Cut the eleven rule functions out of <code>main.rs</code> and paste them into <code>rules.rs</code>.</p>
<p>Leave <code>attack()</code> where it is.</p>
<p>It takes a <code>Player</code> and an <code>Enemy</code>, so it is not one of our plain-value rules. It belongs with the game.</p>
<p>The tests for those rules should move too.</p>
<p>Nine of them check nothing but rules:</p>
<pre><code class="language-text">click_damage_adds_base_and_bonus
health_never_drops_below_zero
zero_health_is_defeated
later_stages_have_tougher_enemies
later_stages_pay_better
levels_need_more_xp_as_they_go
levels_make_the_player_hit_harder
idle_damage_grows_with_level
upgrades_cost_more_each_time
</code></pre>
<p>Cut each one out of the <code>tests</code> module in <code>main.rs</code>, including the <code>#[test]</code> line above it, and give <code>rules.rs</code> a <code>tests</code> module of its own at the bottom:</p>
<pre><code class="language-rust">#[cfg(test)]
mod tests {
    use super::*;

    // the nine rule tests go here
}
</code></pre>
<p>The <code>use super::*;</code> line means:</p>
<blockquote>
<p>Bring in everything from the module above me.</p>
</blockquote>
<p>Now <code>main.rs</code> needs to find its rules again.</p>
<p>Add this to the very top of <code>main.rs</code>, above everything else:</p>
<pre><code class="language-rust">mod rules;

use rules::{
    base_damage_for_level, click_damage,
    click_upgrade_bonus, click_upgrade_cost,
    enemy_health_for_stage, gold_reward_for_stage,
    health_after_hit, idle_damage_for_level,
    is_defeated, xp_reward_for_stage,
    xp_to_next_level,
};
</code></pre>
<p><code>mod rules;</code> says:</p>
<blockquote>
<p>There is a module called <code>rules</code>, and its code lives in <code>src/rules.rs</code>.</p>
</blockquote>
<p>The <code>use</code> line brings its functions into <code>main.rs</code> by name, so the rest of the file can keep calling <code>click_damage()</code> exactly as before.</p>
<p>Build it:</p>
<pre><code class="language-bash">cargo build
</code></pre>
<p>Rust refuses:</p>
<pre><code class="language-text">error[E0603]: function `base_damage_for_level` is private
 --&gt; src/main.rs:4:5
  |
4 |     base_damage_for_level,
  |     ^^^^^^^^^^^^^^^^^^^^^ private function
  |
note: the function `base_damage_for_level` is defined here
 --&gt; src/rules.rs:32:1
  |
32 | fn base_damage_for_level(level: u32) -&gt; u32 {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
</code></pre>
<p>You will get one of these for every function.</p>
<p>That is not a bug in what we did.</p>
<p>It is our first encounter with one of Rust's module boundaries:</p>
<blockquote>
<p><strong>By default, an item is visible inside its module and the modules beneath it. Sibling modules and outside code cannot use it unless we open that boundary.</strong></p>
</blockquote>
<p><code>main.rs</code> sits outside the <code>rules</code> module.</p>
<p>It cannot reach those functions yet.</p>
<p>The simplest way to open that boundary is <code>pub</code>, short for <em>public</em>.</p>
<p>Put it in front of each of the eleven functions:</p>
<pre><code class="language-rust">pub fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}
</code></pre>
<p>Run both:</p>
<pre><code class="language-bash">cargo test
cargo run
</code></pre>
<p>Twenty-one tests pass, and the output is the same as before.</p>
<p>The count should stay at twenty-one after every move in this article.</p>
<p>We moved eleven functions and nine tests into another file, and nothing changed.</p>
<p>That is what a good move looks like.</p>
<hr />
<h2>The enemy</h2>
<p><code>Enemy</code> is next because it is the smallest.</p>
<p>Create <code>src/enemy.rs</code>, and move the <code>Enemy</code> struct and its <code>impl</code> block into it.</p>
<p>The new file needs to find the rules it uses, so it starts like this:</p>
<pre><code class="language-rust">use crate::rules::{
    enemy_health_for_stage,
    gold_reward_for_stage,
    xp_reward_for_stage,
};
</code></pre>
<p><code>crate</code> means the top of the crate this file belongs to.</p>
<p>A path beginning with <code>crate::</code> starts there no matter which module we are currently inside.</p>
<p>For now, all of these modules still belong to the executable crate.</p>
<p>In a moment we are going to move them into a library crate, and that distinction will matter.</p>
<p>Back in <code>main.rs</code>, add:</p>
<pre><code class="language-rust">mod enemy;
</code></pre>
<p>under <code>mod rules;</code>, and:</p>
<pre><code class="language-rust">use enemy::Enemy;
</code></pre>
<p>under the rules import.</p>
<p>Build again.</p>
<p>Rust has an opinion:</p>
<pre><code class="language-text">error[E0603]: struct `Enemy` is private
 --&gt; src/main.rs:4:12
  |
4 | use enemy::Enemy;
  |            ^^^^^ private struct
</code></pre>
<p>Same boundary, same first fix:</p>
<pre><code class="language-rust">pub struct Enemy
</code></pre>
<p>Build again, and Rust has a few more opinions.</p>
<p>Making the struct public does not automatically make all of its fields public.</p>
<p>Each field has its own visibility.</p>
<p>So does <code>Enemy::for_stage()</code>.</p>
<p>For now, here is the finished file:</p>
<pre><code class="language-rust">use crate::rules::{
    enemy_health_for_stage,
    gold_reward_for_stage,
    xp_reward_for_stage,
};

pub struct Enemy {
    pub name: String,
    pub health: u32,
    pub gold_reward: u32,
    pub xp_reward: u32,
}

impl Enemy {
    pub fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}
</code></pre>
<p>Everything is <code>pub</code> for now.</p>
<p>That is too generous.</p>
<p>We are going to fix it on purpose once the boundaries are in place.</p>
<p>Run:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>Twenty-one pass.</p>
<p>You may also see warnings that some imports in <code>main.rs</code> are unused.</p>
<p>That is true.</p>
<p><code>Enemy</code> was the only caller of <code>enemy_health_for_stage()</code>, <code>gold_reward_for_stage()</code>, and <code>xp_reward_for_stage()</code>, and it just moved.</p>
<p>Delete those names from the <code>use rules::{...}</code> list in <code>main.rs</code>.</p>
<hr />
<h2>The player</h2>
<p>Same move, bigger file.</p>
<p>Create <code>src/player.rs</code>.</p>
<p>Move in:</p>
<ul>
<li><code>Player</code></li>
<li><code>PurchaseOutcome</code></li>
<li>the <code>impl Player</code> block</li>
</ul>
<p><code>PurchaseOutcome</code> goes with <code>Player</code> because it is the player's vocabulary.</p>
<p>It is how a purchase answers.</p>
<p>At the top of the file, import the rules <code>Player</code> uses:</p>
<pre><code class="language-rust">use crate::rules::{
    base_damage_for_level,
    click_upgrade_bonus,
    click_upgrade_cost,
    xp_to_next_level,
};
</code></pre>
<p>Then add <code>pub</code> to the struct, its four fields, the enum, and every function in <code>impl Player</code>.</p>
<p>An enum's variants follow the enum's visibility.</p>
<p>Make <code>PurchaseOutcome</code> public, and its <code>Purchased</code> and <code>NotEnoughGold</code> variants are available with it.</p>
<p>Four tests are about the player and nothing else:</p>
<pre><code class="language-text">leveling_up_keeps_leftover_xp
a_big_reward_can_skip_levels
buying_an_upgrade_needs_gold
buying_an_upgrade_spends_gold
</code></pre>
<p>Move those into a <code>tests</code> module at the bottom of <code>player.rs</code>, the same way we did with the rules.</p>
<p>In <code>main.rs</code>, add:</p>
<pre><code class="language-rust">mod player;
</code></pre>
<p>and:</p>
<pre><code class="language-rust">use player::{Player, PurchaseOutcome};
</code></pre>
<p><code>Game</code> and <code>attack()</code> still live in <code>main.rs</code> for now, so <code>main.rs</code> still needs <code>Player</code>.</p>
<p>That import drops out again in the next section.</p>
<p>Run:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>Twenty-one pass.</p>
<p>Clean up the new unused-import warnings the same way: remove <code>base_damage_for_level</code>, <code>click_upgrade_bonus</code>, and <code>click_upgrade_cost</code> from the <code>use rules::{...}</code> list in <code>main.rs</code>.</p>
<hr />
<h2>The game</h2>
<p>Last one.</p>
<p>Create <code>src/game.rs</code> and move in:</p>
<ul>
<li><code>Game</code></li>
<li>its <code>impl</code> block</li>
<li><code>attack()</code></li>
</ul>
<p>It needs three things from the other modules:</p>
<pre><code class="language-rust">use crate::enemy::Enemy;
use crate::player::{Player, PurchaseOutcome};
use crate::rules::{
    click_damage,
    health_after_hit,
    idle_damage_for_level,
    is_defeated,
};
</code></pre>
<p>Add <code>pub</code> to the struct and its three fields.</p>
<p>Then add it to the four functions the outside world is going to call:</p>
<pre><code class="language-rust">pub fn new() -&gt; Game
pub fn click(&amp;mut self)
pub fn tick(&amp;mut self, seconds: u32)
pub fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome
</code></pre>
<p>Now look at what is <strong>not</strong> on that list.</p>
<p><code>resolve_hit()</code> stays private.</p>
<p><code>defeat_enemy()</code> stays private.</p>
<p>So does <code>attack()</code>.</p>
<p>Nothing outside <code>game.rs</code> has a reason to call them.</p>
<p>When a slime dies, the game decides what that means.</p>
<p>A caller should not get to say:</p>
<blockquote>
<p>Pretend that enemy just died.</p>
</blockquote>
<p>We will close the field loophole shortly.</p>
<p>That is what privacy is for.</p>
<p>It is not about hiding code for the sake of hiding it.</p>
<p>It is about deciding what the rest of the program is allowed to depend on.</p>
<p>Eight tests are about the game, including the one for <code>attack()</code>:</p>
<pre><code class="language-text">attack_reduces_enemy_health
a_new_game_starts_at_stage_one
clicking_damages_the_enemy
defeating_an_enemy_pays_the_player
one_second_is_one_idle_hit
zero_seconds_changes_nothing
enough_time_defeats_the_enemy
one_long_tick_matches_many_short_ones
</code></pre>
<p>Move them to the bottom of <code>game.rs</code>.</p>
<p>In <code>main.rs</code>, add:</p>
<pre><code class="language-rust">mod game;
</code></pre>
<p>and:</p>
<pre><code class="language-rust">use game::Game;
</code></pre>
<p>At this point, the <code>tests</code> module in <code>main.rs</code> is empty.</p>
<p>Delete it.</p>
<p>You can also remove <code>Player</code> from:</p>
<pre><code class="language-rust">use player::{Player, PurchaseOutcome};
</code></pre>
<p>because only <code>Game</code> needs it now.</p>
<p>Clear out any remaining unused imports.</p>
<p>The top of <code>main.rs</code> should look like this:</p>
<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;

use game::Game;
use player::PurchaseOutcome;
use rules::{click_damage, xp_to_next_level};
</code></pre>
<p>Under that, only <code>main()</code> is left.</p>
<p>Run both:</p>
<pre><code class="language-bash">cargo test
cargo run
</code></pre>
<p>Twenty-one tests.</p>
<p>Same output.</p>
<p>Four files hold the game.</p>
<p>One holds the printing.</p>
<p>But we have not built the wall yet.</p>
<p><code>main.rs</code> still declares all four modules.</p>
<p>They are part of the executable crate.</p>
<p>Nothing outside that executable could use them as a library.</p>
<hr />
<h2>Build the wall</h2>
<p>A Cargo package can contain both a library target and a binary target:</p>
<pre><code class="language-text">src/lib.rs    library
src/main.rs   executable
</code></pre>
<p>Rust compiles them as separate crates.</p>
<p>The binary can depend on the library.</p>
<p>That gives us the wall we have been looking for.</p>
<p>A <strong>crate</strong> is one compiled unit of Rust code.</p>
<p>Until now, our project has had one crate: the executable.</p>
<p>Now our package will contain two:</p>
<pre><code class="language-text">library crate
    ↑
binary crate
</code></pre>
<p>They live in the same project, but Rust treats them as separate pieces of code.</p>
<p>Create <code>src/lib.rs</code>, and move the four <code>mod</code> lines out of <code>main.rs</code> and into it:</p>
<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;
</code></pre>
<p>The project is named:</p>
<pre><code class="language-text">rustbound-idle
</code></pre>
<p>Rust turns the hyphen into an underscore when we refer to the library in code:</p>
<pre><code class="language-text">rustbound_idle
</code></pre>
<p>At the top of <code>main.rs</code>, replace all the old <code>mod</code> and <code>use</code> lines with:</p>
<pre><code class="language-rust">use rustbound_idle::{Game, PurchaseOutcome};
</code></pre>
<p>Build it:</p>
<pre><code class="language-bash">cargo build
</code></pre>
<p>Rust complains.</p>
<p>The first problem looks like this:</p>
<pre><code class="language-text">error[E0432]: unresolved imports
  `rustbound_idle::Game`,
  `rustbound_idle::PurchaseOutcome`
 --&gt; src/main.rs:1:22
  |
  |   no `Game` in the root
  |   no `PurchaseOutcome` in the root
</code></pre>
<p>The library has a <code>game</code> module.</p>
<p>That module has a <code>Game</code>.</p>
<p>But <code>mod game;</code> is private.</p>
<p>From outside the library, there is no public path to <code>Game</code>.</p>
<p>As far as the binary can tell, the library's front door is empty.</p>
<p>We choose what appears there.</p>
<p>That is what <code>pub use</code> is for:</p>
<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;

pub use enemy::Enemy;
pub use game::Game;
pub use player::{Player, PurchaseOutcome};
</code></pre>
<p><code>pub use</code> means:</p>
<blockquote>
<p>Take this item from inside the library and make it available through this public path.</p>
</blockquote>
<p>Now code using the library can write:</p>
<pre><code class="language-rust">rustbound_idle::Game
</code></pre>
<p>Where <code>Game</code> actually lives inside the library is no longer the caller's problem.</p>
<p>Build again.</p>
<p>Now the remaining errors are about rules:</p>
<pre><code class="language-text">error[E0425]: cannot find function `xp_to_next_level` in this scope
error[E0425]: cannot find function `click_damage` in this scope
error[E0425]: cannot find function `click_damage` in this scope
</code></pre>
<p>Those calls used to work because <code>main.rs</code> imported the rules directly.</p>
<p>Now <code>rules</code> is private inside the library.</p>
<p>There are two ways we could fix that.</p>
<p>We could put <code>rules</code> on the front door too:</p>
<pre><code class="language-rust">pub mod rules;
</code></pre>
<p>Then <code>main.rs</code> could import those formulas and keep doing this:</p>
<pre><code class="language-rust">click_damage(
    game.player.base_damage(),
    game.player.bonus_damage()
)
</code></pre>
<p>That would compile.</p>
<p>But think about what it means.</p>
<p>Every formula in the game becomes part of the library's public API.</p>
<p>And the screen keeps doing game arithmetic.</p>
<p>Today the screen is a terminal.</p>
<p>Later it will be a browser.</p>
<p>If both of them know how to calculate damage, we now have multiple callers depending on an internal game rule.</p>
<p>The rules should live in one place.</p>
<p>The screen should ask for answers.</p>
<p>So we take the second path.</p>
<p>Leave <code>rules</code> private.</p>
<p>Let <code>Player</code> provide the information the outside world needs.</p>
<p>Add two methods inside <code>impl Player</code>, after <code>add_xp()</code>:</p>
<pre><code class="language-rust">pub fn xp_needed(&amp;self) -&gt; u32 {
    xp_to_next_level(self.level)
}

pub fn click_damage(&amp;self) -&gt; u32 {
    click_damage(
        self.base_damage(),
        self.bonus_damage()
    )
}
</code></pre>
<p>The method and the rule share a name.</p>
<p>That is fine.</p>
<p>Inside the method:</p>
<pre><code class="language-rust">click_damage(...)
</code></pre>
<p>with no <code>self.</code> in front of it means the imported rule.</p>
<p>The method is the player asking that rule a question about itself.</p>
<p>First, add <code>click_damage</code> to the <code>use crate::rules::{...}</code> list at the top of <code>player.rs</code>.</p>
<p>It was not there before because <code>Player</code> used to leave that arithmetic to <code>attack()</code>.</p>
<p>While we are in there, <code>add_xp()</code> has been calling:</p>
<pre><code class="language-rust">xp_to_next_level(self.level)
</code></pre>
<p>twice.</p>
<p>Now that the player has a method for that question, replace its loop with:</p>
<pre><code class="language-rust">while self.xp &gt;= self.xp_needed() {
    self.xp -= self.xp_needed();
    self.level = self.level.saturating_add(1);
}
</code></pre>
<p>The loop is still the same loop.</p>
<p><code>xp_to_next_level</code> stays imported because <code>xp_needed()</code> uses it.</p>
<p><code>attack()</code> in <code>game.rs</code> can lean on the other new method too:</p>
<pre><code class="language-rust">fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    enemy.health =
        health_after_hit(
            enemy.health,
            player.click_damage()
        );
}
</code></pre>
<p>It no longer needs the <code>click_damage</code> rule, so remove that name from the <code>use</code> statement at the top of <code>game.rs</code>.</p>
<p>Last, <code>main()</code> gets simpler.</p>
<p>Where it said:</p>
<pre><code class="language-rust">xp_to_next_level(game.player.level)
</code></pre>
<p>it now says:</p>
<pre><code class="language-rust">game.player.xp_needed()
</code></pre>
<p>And where it said:</p>
<pre><code class="language-rust">click_damage(
    game.player.base_damage(),
    game.player.bonus_damage()
)
</code></pre>
<p>it now says:</p>
<pre><code class="language-rust">game.player.click_damage()
</code></pre>
<p>That second replacement happens twice in <code>main()</code>.</p>
<p>Build, test, and run:</p>
<pre><code class="language-bash">cargo test
cargo run
</code></pre>
<p>Same twenty-one tests.</p>
<p>Same output.</p>
<p>Cargo also prints test blocks for <code>main.rs</code> and doc-tests that say:</p>
<pre><code class="language-text">running 0 tests
</code></pre>
<p>That is normal.</p>
<p>And <code>main.rs</code> no longer knows a single game formula.</p>
<hr />
<h2>Make the front door smaller</h2>
<p>We said everything was too <code>pub</code>.</p>
<p>Time to fix that.</p>
<p>Right now <code>main()</code> can see far more than it needs.</p>
<p>Try it.</p>
<p>Add this line just after:</p>
<pre><code class="language-rust">let mut game = Game::new();
</code></pre>
<pre><code class="language-rust">game.player.add_gold(1_000_000);
</code></pre>
<p>It compiles.</p>
<p>A screen can hand the player a million gold, and the game never gets a say.</p>
<p>Let us close that off.</p>
<p>Rust has a visibility setting between fully private and fully public:</p>
<pre><code class="language-rust">pub(crate)
</code></pre>
<p>It means:</p>
<blockquote>
<p>Visible anywhere inside this crate, but not outside it.</p>
</blockquote>
<p>Our library can use the item freely.</p>
<p><code>main.rs</code> belongs to a different crate now, so it cannot.</p>
<p>Go through the files and tighten everything <code>main()</code> does not need.</p>
<p>The rule for now is:</p>
<blockquote>
<p><code>pub</code> for what outside code needs. <code>pub(crate)</code> or private for everything else.</p>
</blockquote>
<p>In <code>rules.rs</code>, change all eleven:</p>
<pre><code class="language-rust">pub fn
</code></pre>
<p>to:</p>
<pre><code class="language-rust">pub(crate) fn
</code></pre>
<p>In <code>enemy.rs</code>:</p>
<ul>
<li>change <code>for_stage()</code> to <code>pub(crate)</code></li>
<li>make <code>gold_reward</code> and <code>xp_reward</code> <code>pub(crate)</code></li>
<li>leave <code>name</code> and <code>health</code> public because <code>main()</code> displays them</li>
</ul>
<p>In <code>player.rs</code>, change:</p>
<ul>
<li><code>new</code></li>
<li><code>add_gold</code></li>
<li><code>add_xp</code></li>
<li><code>buy_click_upgrade</code></li>
</ul>
<p>to <code>pub(crate)</code>.</p>
<p>Take <code>pub</code> off <code>base_damage()</code> and <code>bonus_damage()</code> entirely.</p>
<p>Only <code>Player</code> needs those now.</p>
<p>What remains fully public is what the current screen needs:</p>
<pre><code class="language-text">Game
  new
  click
  tick
  buy_click_upgrade
  stage
  player
  enemy

Player
  gold
  xp
  level
  click_upgrade_level
  xp_needed
  click_damage

Enemy
  name
  health

PurchaseOutcome
</code></pre>
<p>Now try this again:</p>
<pre><code class="language-rust">game.player.add_gold(1_000_000);
</code></pre>
<p>It fails.</p>
<p><code>add_gold()</code> is <code>pub(crate)</code>, and <code>main.rs</code> is outside the library crate.</p>
<p>Delete that line.</p>
<p>Run:</p>
<pre><code class="language-bash">cargo test
cargo run
</code></pre>
<p>Nothing changes.</p>
<p>That is the point.</p>
<p>If tightening the boundary had broken <code>main()</code>, it would mean <code>main()</code> depended on something we did not actually want to promise publicly.</p>
<p>Now try to cheat another way.</p>
<p>In <code>main()</code>, add:</p>
<pre><code class="language-rust">let secret = game.enemy.gold_reward;
</code></pre>
<p>Rust says no:</p>
<pre><code class="language-text">error[E0616]: field `gold_reward` of struct `Enemy` is private
</code></pre>
<p>Delete it.</p>
<p>Now try reaching for an internal damage detail.</p>
<p>Replace:</p>
<pre><code class="language-rust">game.player.click_damage()
</code></pre>
<p>with:</p>
<pre><code class="language-rust">game.player.base_damage()
</code></pre>
<p>Rust refuses again:</p>
<pre><code class="language-text">error[E0624]: method `base_damage` is private
</code></pre>
<p>Put it back.</p>
<p>Those errors are the wall doing its job.</p>
<hr />
<h2>A test from the outside</h2>
<p>Our twenty-one tests all live <strong>inside</strong> the library, next to the code they check.</p>
<p>That is useful.</p>
<p>Those tests can work closely with the modules they belong to.</p>
<p>But it leaves another question:</p>
<blockquote>
<p>Does the library work for code that only gets to use the public front door?</p>
</blockquote>
<p>Cargo has a place for that.</p>
<p>Create a folder called <code>tests</code> next to <code>src</code>, and put a file in it:</p>
<pre><code class="language-text">rustbound-idle/
├── src/
└── tests/
    └── idle_hour.rs
</code></pre>
<p>Each Rust file directly inside <code>tests/</code> is compiled as a separate <strong>integration test crate</strong>.</p>
<p>It uses the library the same way outside code does: through the public API.</p>
<p>This is the first code in the project that sees the game the way a stranger would.</p>
<p>Put this in <code>tests/idle_hour.rs</code>:</p>
<pre><code class="language-rust">use rustbound_idle::Game;

#[test]
fn an_hour_away_moves_the_game_forward() {
    let mut game = Game::new();

    game.tick(3600);

    assert_eq!(game.stage, 128);
    assert_eq!(game.player.level, 47);
}
</code></pre>
<p>The terminal program clicked through three stages before it waited an hour.</p>
<p>This test waits an hour from a brand-new game, so the numbers are different:</p>
<pre><code class="language-text">stage 128
level 47
</code></pre>
<p>Run everything:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>Cargo prints a block for each piece it tests.</p>
<p>You should see:</p>
<ul>
<li>the twenty-one tests inside the library</li>
<li>the new integration test in <code>tests/idle_hour.rs</code></li>
<li>blocks for <code>main.rs</code> and doc-tests that say <code>running 0 tests</code></li>
</ul>
<p>That is fine.</p>
<p>Twenty-two tests in all.</p>
<hr />
<h2>One last complaint</h2>
<p>There is one more thing, and it has been waiting for us.</p>
<p>Clippy is Rust's linter.</p>
<p>It looks for patterns that compile but could be clearer, more idiomatic, or less error-prone.</p>
<p>If:</p>
<pre><code class="language-bash">cargo clippy
</code></pre>
<p>is not available, install the component first:</p>
<pre><code class="language-bash">rustup component add clippy
</code></pre>
<p>Then run:</p>
<pre><code class="language-bash">cargo clippy
</code></pre>
<p>Clippy has a suggestion:</p>
<pre><code class="language-text">warning: you should consider adding a `Default` implementation for `Game`
</code></pre>
<p>It did not complain before because <code>Game::new()</code> was not part of a public API.</p>
<p>Now <code>Game</code> is public, and so is its zero-argument <code>new()</code>.</p>
<p>Clippy notices that callers may reasonably expect the standard Rust <code>Default</code> trait too.</p>
<p><code>Default</code> describes a type's standard starting value.</p>
<p>We already have one:</p>
<pre><code class="language-rust">Game::new()
</code></pre>
<p>So we can say that explicitly:</p>
<pre><code class="language-rust">impl Default for Game {
    fn default() -&gt; Game {
        Game::new()
    }
}
</code></pre>
<p>Put it in <code>game.rs</code>, between the <code>impl Game</code> block and <code>attack()</code>.</p>
<p>Now:</p>
<pre><code class="language-rust">Game::new()
</code></pre>
<p>and:</p>
<pre><code class="language-rust">Game::default()
</code></pre>
<p>produce the same starting game.</p>
<p>Run Clippy again.</p>
<p>It should be quiet.</p>
<hr />
<h2>The complete program</h2>
<p>If you are only reading along, skip to the last two sections.</p>
<p>If you have been following along in your editor, here is everything in one place.</p>
<p>Your project should now look like this:</p>
<pre><code class="language-text">rustbound-idle/
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── main.rs
│   ├── rules.rs
│   ├── enemy.rs
│   ├── player.rs
│   └── game.rs
└── tests/
    └── idle_hour.rs
</code></pre>
<h3><code>src/lib.rs</code></h3>
<pre><code class="language-rust">mod enemy;
mod game;
mod player;
mod rules;

pub use enemy::Enemy;
pub use game::Game;
pub use player::{Player, PurchaseOutcome};
</code></pre>
<h3><code>src/main.rs</code></h3>
<pre><code class="language-rust">use rustbound_idle::{Game, PurchaseOutcome};

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 3 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                game.player.xp_needed()
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    game.player.click_damage()
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name,
                game.enemy.health
            );
        }

        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                game.player.click_damage(),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold =&gt; {}
        }
    }

    let stage = game.stage;
    let level = game.player.level;
    let gold = game.player.gold;

    game.tick(3600);

    println!("One hour away.");
    println!(
        "Stage {} -&gt; {}  Level {} -&gt; {}  Gold {} -&gt; {}",
        stage,
        game.stage,
        level,
        game.player.level,
        gold,
        game.player.gold
    );
}
</code></pre>
<h3><code>src/rules.rs</code></h3>
<pre><code class="language-rust">pub(crate) fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

pub(crate) fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

pub(crate) fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

pub(crate) fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

pub(crate) fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}

pub(crate) fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}

pub(crate) fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

pub(crate) fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}

pub(crate) fn idle_damage_for_level(level: u32) -&gt; u32 {
    level
}

pub(crate) fn click_upgrade_bonus(upgrades: u32) -&gt; u32 {
    upgrades.saturating_mul(2)
}

pub(crate) fn click_upgrade_cost(upgrades: u32) -&gt; u32 {
    upgrades
        .saturating_add(1)
        .saturating_mul(10)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn idle_damage_grows_with_level() {
        assert_eq!(idle_damage_for_level(1), 1);
        assert_eq!(idle_damage_for_level(2), 2);
    }

    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }
}
</code></pre>
<h3><code>src/enemy.rs</code></h3>
<pre><code class="language-rust">use crate::rules::{
    enemy_health_for_stage,
    gold_reward_for_stage,
    xp_reward_for_stage,
};

pub struct Enemy {
    pub name: String,
    pub health: u32,
    pub(crate) gold_reward: u32,
    pub(crate) xp_reward: u32,
}

impl Enemy {
    pub(crate) fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}
</code></pre>
<h3><code>src/player.rs</code></h3>
<pre><code class="language-rust">use crate::rules::{
    base_damage_for_level,
    click_damage,
    click_upgrade_bonus,
    click_upgrade_cost,
    xp_to_next_level,
};

pub struct Player {
    pub gold: u32,
    pub xp: u32,
    pub level: u32,
    pub click_upgrade_level: u32,
}

#[derive(Debug, PartialEq)]
pub enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}

impl Player {
    pub(crate) fn new() -&gt; Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }

    pub(crate) fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    pub(crate) fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= self.xp_needed() {
            self.xp -= self.xp_needed();
            self.level = self.level.saturating_add(1);
        }
    }

    pub fn xp_needed(&amp;self) -&gt; u32 {
        xp_to_next_level(self.level)
    }

    pub fn click_damage(&amp;self) -&gt; u32 {
        click_damage(
            self.base_damage(),
            self.bonus_damage()
        )
    }

    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }

    fn bonus_damage(&amp;self) -&gt; u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }

    pub(crate) fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        let cost =
            click_upgrade_cost(self.click_upgrade_level);

        if self.gold &lt; cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level =
            self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(
            player.buy_click_upgrade(),
            PurchaseOutcome::NotEnoughGold
        );
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(
            player.buy_click_upgrade(),
            PurchaseOutcome::Purchased
        );
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }
}
</code></pre>
<h3><code>src/game.rs</code></h3>
<pre><code class="language-rust">use crate::enemy::Enemy;
use crate::player::{Player, PurchaseOutcome};
use crate::rules::{
    health_after_hit,
    idle_damage_for_level,
    is_defeated,
};

pub struct Game {
    pub stage: u32,
    pub player: Player,
    pub enemy: Enemy,
}

impl Game {
    pub fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    pub fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);
        self.resolve_hit();
    }

    pub fn tick(&amp;mut self, seconds: u32) {
        for _ in 0..seconds {
            let damage =
                idle_damage_for_level(self.player.level);

            self.enemy.health =
                health_after_hit(
                    self.enemy.health,
                    damage
                );

            self.resolve_hit();
        }
    }

    fn resolve_hit(&amp;mut self) {
        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }

    pub fn buy_click_upgrade(
        &amp;mut self
    ) -&gt; PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
}

impl Default for Game {
    fn default() -&gt; Game {
        Game::new()
    }
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    enemy.health =
        health_after_hit(
            enemy.health,
            player.click_damage()
        );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 5);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 15);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }

    #[test]
    fn one_second_is_one_idle_hit() {
        let mut game = Game::new();

        game.tick(1);

        assert_eq!(game.enemy.health, 19);
    }

    #[test]
    fn zero_seconds_changes_nothing() {
        let mut game = Game::new();

        game.tick(0);

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
    }

    #[test]
    fn enough_time_defeats_the_enemy() {
        let mut game = Game::new();

        game.tick(20);

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }

    #[test]
    fn one_long_tick_matches_many_short_ones() {
        let mut long = Game::new();
        let mut short = Game::new();

        long.tick(45);

        for _ in 0..45 {
            short.tick(1);
        }

        assert_eq!(long.stage, short.stage);
        assert_eq!(
            long.enemy.health,
            short.enemy.health
        );
        assert_eq!(
            long.player.gold,
            short.player.gold
        );
        assert_eq!(
            long.player.xp,
            short.player.xp
        );
        assert_eq!(
            long.player.level,
            short.player.level
        );
    }
}
</code></pre>
<h3><code>tests/idle_hour.rs</code></h3>
<pre><code class="language-rust">use rustbound_idle::Game;

#[test]
fn an_hour_away_moves_the_game_forward() {
    let mut game = Game::new();

    game.tick(3600);

    assert_eq!(game.stage, 128);
    assert_eq!(game.player.level, 47);
}
</code></pre>
<p>Run everything:</p>
<pre><code class="language-bash">cargo run
cargo test
cargo clippy
</code></pre>
<p>The game should clear three stages, buy one upgrade, and spend an hour away.</p>
<p>All twenty-two tests should pass.</p>
<p>Clippy should be quiet.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li>modules, and <code>mod</code> to declare one</li>
<li>organizing top-level modules into separate files</li>
<li><code>use</code>, and <code>crate::</code> paths</li>
<li>Rust's default privacy boundaries</li>
<li><code>pub</code></li>
<li><code>pub(crate)</code></li>
<li><code>pub use</code>, for choosing what appears on a library's front door</li>
<li>a library crate and a binary crate in the same Cargo package</li>
<li>tests that live beside their code</li>
<li>integration tests in <code>tests/</code></li>
<li><code>Default</code>, which Clippy asked us to provide</li>
</ul>
<p>Two ideas are worth keeping.</p>
<p>The first is that privacy is not about hiding.</p>
<p>It is about deciding what everyone else is allowed to depend on.</p>
<p>A private function gives us freedom.</p>
<p>We can rename it, reorganize it, or replace its implementation without outside code depending on that detail, as long as the public behavior stays the same.</p>
<p><code>resolve_hit()</code> can move.</p>
<p><code>base_damage()</code> can change.</p>
<p>Code outside their visibility boundary does not get to build itself around those details.</p>
<p>The second idea is where the line went.</p>
<p>We did not decide to build an engine.</p>
<p>We sorted the code, and the sorting kept asking one question:</p>
<blockquote>
<p><em>Does this need to know about a screen?</em></p>
</blockquote>
<p>For everything except <code>main()</code>, the answer was no.</p>
<p>That is the heart of what our little game engine is becoming.</p>
<p>It is not a graphics library.</p>
<p>It does not open a window.</p>
<p>It does not print to a terminal.</p>
<p>It does not know whether a browser even exists.</p>
<p>It owns rules and state.</p>
<p>The game's four main commands are:</p>
<pre><code class="language-text">new
click
tick
buy_click_upgrade
</code></pre>
<p>We still expose some state so <code>main()</code> can display it.</p>
<p>That part of the front door is wider than I want.</p>
<p><code>Game</code>, <code>Player</code>, and <code>Enemy</code> still have public fields, which means <code>main()</code> could write:</p>
<pre><code class="language-rust">game.enemy.health = 0;
</code></pre>
<p>and the compiler would allow it.</p>
<p>That is a loose end.</p>
<p>We are going to lean on it soon.</p>
<hr />
<h2>It cannot remember</h2>
<p>Our game can simulate an hour instantly.</p>
<p>It cannot survive the program ending.</p>
<p>Close the terminal and everything is gone:</p>
<pre><code class="language-text">stage
gold
XP
level
upgrades
</code></pre>
<p>An idle game that forgets you the moment you leave is not much of an idle game.</p>
<p>Next time, the game learns to save itself.</p>
<p>Not to a file.</p>
<p>Not to a browser.</p>
<p>Not to a server.</p>
<p>The library will not know where saves go, and it should not.</p>
<p>It will only know how to say, in a form that can be written down:</p>
<blockquote>
<p>This is everything about me.</p>
</blockquote>
<p>And later, given that same form:</p>
<blockquote>
<p>I remember.</p>
</blockquote>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Idle Is Not a Timer.]]></title><description><![CDATA[Make your Rust idle game progress while you are away. Add tick(seconds), a for loop, and tests that run an hour of game time instantly.]]></description><link>https://journeytocode.hashnode.dev/idle-is-not-a-timer</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/idle-is-not-a-timer</guid><category><![CDATA[Rust]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[idle game]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Fri, 23 Oct 2026 16:25:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/1e3417ce-a46a-4844-aef0-ca02470bf53d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our game has a progression loop now.</p>
<p>Kill a slime, get paid, level up, buy damage, kill a tougher slime.</p>
<p>It still stops the moment you stop clicking.</p>
<p>So today we are going to make time part of the game.</p>
<p>Not a timer. Not a clock. Not <code>sleep</code>.</p>
<p>Just a number.</p>
<p>Because the game will never look at a clock, we can test a full hour away in a fraction of a second.</p>
<p>By the end, an hour away from the game is one line:</p>
<pre><code class="language-rust">game.tick(3600);
</code></pre>
<p>And here is what the game looks like when you come back:</p>
<pre><code class="language-text">One hour away.
Stage 4 -&gt; 131  Level 2 -&gt; 48  Gold 11 -&gt; 17410
</code></pre>
<hr />
<h2>Our starting point</h2>
<p>If you are only reading along, you can skip past this code to "Time is just a number."</p>
<p>If you are building along, you need three structs, an enum, three <code>impl</code> blocks, eleven small functions, and sixteen tests:</p>
<pre><code class="language-rust">struct Player {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}

struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

#[derive(Debug, PartialEq)]
enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}

impl Player {
    fn new() -&gt; Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }

    fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }

    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }

    fn bonus_damage(&amp;self) -&gt; u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }

    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold &lt; cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
}

impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);

        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }

    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}

fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}

fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}

fn click_upgrade_bonus(upgrades: u32) -&gt; u32 {
    upgrades.saturating_mul(2)
}

fn click_upgrade_cost(upgrades: u32) -&gt; u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage());

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 8 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage())
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }

        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                click_damage(game.player.base_damage(), game.player.bonus_damage()),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold =&gt; {}
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 5);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }

    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::NotEnoughGold);
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased);
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 15);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }
}
</code></pre>
<p>Run it, and the player clears eight stages and buys three upgrades.</p>
<p>Then the program is over, and so is all the progress, because nothing in it ever happens on its own.</p>
<hr />
<h2>Time is just a number</h2>
<p>An idle game has to keep going while nobody is looking.</p>
<p>The obvious way to build that is to give the game a clock.</p>
<p>Start a timer. Every second, do some work. When the player leaves, keep the timer running.</p>
<p>That sounds simple, and it is a trap.</p>
<p>A game that watches a clock is a game that has to be tested by waiting, unless you build a fake clock, and then you have rebuilt the idea we are about to use anyway.</p>
<p>Want to know what happens after an hour away? Wait an hour.</p>
<p>So let's turn it around.</p>
<p>The game will not ask what time it is.</p>
<p>Whoever is running the game will tell it how much time went by:</p>
<pre><code class="language-rust">game.tick(10);
</code></pre>
<p>Ten seconds passed. What happened?</p>
<p>Game code often uses the word <em>tick</em> for advancing simulation time, so we will borrow the name.</p>
<p>The contract is one sentence:</p>
<blockquote>
<p>Tell me how many seconds passed. I will tell you what the game became.</p>
</blockquote>
<p><code>tick</code> will be a method on <code>Game</code>, and the only thing it takes is that number.</p>
<p>The number is the only thing about time that Rust ever sees.</p>
<p>Later in this series, a web page will measure real time and hand the game that number.</p>
<p>For now, our tests can hand it any number they like.</p>
<hr />
<h2>What does a player do while nobody is clicking?</h2>
<p>Before the game can work out what happened, it needs to know what the player does with spare seconds.</p>
<p>Our player is going to keep fighting.</p>
<p>One hit per second, and the harder the player has leveled up, the harder it hits.</p>
<p>That is a game rule, and we know what to do with game rules by now.</p>
<p>Give it a small function, next to the others:</p>
<pre><code class="language-rust">fn idle_damage_for_level(level: u32) -&gt; u32 {
    level
}
</code></pre>
<p>One damage per second at level 1.</p>
<p>Two at level 2.</p>
<p>It is deliberately small. It is not balanced and it does not need to be yet.</p>
<p>It also ignores the click upgrades on purpose. Those are for clicking.</p>
<p>What matters is that the rule has a name and a test.</p>
<p>Add it at the bottom of the <code>tests</code> module:</p>
<pre><code class="language-rust">#[test]
fn idle_damage_grows_with_level() {
    assert_eq!(idle_damage_for_level(1), 1);
    assert_eq!(idle_damage_for_level(2), 2);
}
</code></pre>
<p>Notice what the function does not take.</p>
<p>It does not take a <code>Player</code>. It does not take a <code>Game</code>.</p>
<p>It takes a level and returns a number. It is the same kind of function as <code>base_damage_for_level()</code>, and it asks the same question: what does this level of progress get you?</p>
<p>If you run <code>cargo build</code> now, Rust warns that <code>idle_damage_for_level</code> is never used.</p>
<p>We are about to use it.</p>
<hr />
<h2>One second at a time</h2>
<p>Now for <code>tick()</code>.</p>
<p>The player hits once per second, so ten seconds is ten hits.</p>
<p>We know exactly how many times to repeat: once per second that passed.</p>
<p>That is not a job for <code>while</code>.</p>
<p>A <code>while</code> loop keeps going until a condition stops being true, like our level-up loop, which cannot know how many levels one reward will cross.</p>
<p>When you know the count in advance, Rust has a loop for that:</p>
<pre><code class="language-rust">for _ in 0..seconds {
    // one second of work
}
</code></pre>
<p><code>0..seconds</code> is a range.</p>
<p>It counts from 0 up to, but not including, <code>seconds</code>.</p>
<p>So <code>0..3</code> gives you 0, 1, and 2. That is three trips through the loop, which is exactly what we want for three seconds.</p>
<p>The <code>_</code> is a placeholder that means:</p>
<blockquote>
<p>I do not need this value.</p>
</blockquote>
<p>A <code>for</code> loop hands us the current number from the range on every trip.</p>
<p>We do not care which second we are on. We only care that one passed.</p>
<p>If we gave the number a name like <code>second</code>, Rust would warn that we never use it.</p>
<p>The <code>_</code> says that is on purpose.</p>
<p>Write the first version inside the <code>impl Game</code> block, right under <code>click()</code>. It is indented because it lives inside that block:</p>
<pre><code class="language-rust">    fn tick(&amp;mut self, seconds: u32) {
        for _ in 0..seconds {
            let damage = idle_damage_for_level(self.player.level);

            self.enemy.health = health_after_hit(self.enemy.health, damage);
        }
    }
</code></pre>
<p>Each trip through the loop works out the damage for the player's current level and hits the enemy with it.</p>
<p>It uses <code>health_after_hit()</code>, the same small rule that has been protecting enemy health since the first article.</p>
<p>That looks finished.</p>
<p>It is not, and we should have a test to say so.</p>
<hr />
<h2>The slime that died and kept being hit</h2>
<p>We have a rule for how fast the player hits.</p>
<p>What we need next is a test for the thing a player cares about: if I wait long enough, does the slime go down?</p>
<p>At level 1, our player hits for 1 per second, and the first slime has 20 health.</p>
<p>Twenty seconds should do it:</p>
<pre><code class="language-rust">#[test]
fn enough_time_defeats_the_enemy() {
    let mut game = Game::new();

    game.tick(20);

    assert_eq!(game.stage, 2);
    assert_eq!(game.enemy.health, 30);
    assert_eq!(game.player.gold, 5);
    assert_eq!(game.player.xp, 6);
}
</code></pre>
<p>Those last numbers should look familiar.</p>
<p>They are the same ones the click version of this test checks: stage 2, a 30 health enemy, 5 gold, 6 XP.</p>
<p>It does not matter whether a slime dies from a click or from a wait.</p>
<p>The player should get paid the same either way.</p>
<p>Run it:</p>
<pre><code class="language-bash">cargo test enough_time
</code></pre>
<p>and it fails:</p>
<pre><code class="language-text">assertion `left == right` failed
  left: 1
 right: 2
</code></pre>
<p>(Rust prints more than that, including a line number and a note about backtraces. This is the part that matters.)</p>
<p><code>left</code> is what the game did. <code>right</code> is what the test expected.</p>
<p>The stage is still 1.</p>
<p>Twenty seconds passed. The slime's health reached zero on the twentieth hit.</p>
<p>And then nothing happened.</p>
<p>Our loop never asks whether the enemy is defeated.</p>
<p>That check lives in <code>click()</code>:</p>
<pre><code class="language-rust">        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
</code></pre>
<p><code>tick()</code> took the hit from <code>click()</code> but left the aftermath behind.</p>
<hr />
<h2>Two ways to land a hit</h2>
<p>There are two ways to hurt an enemy now: a click, and a second of waiting.</p>
<p>Both of them need the same thing to happen afterward.</p>
<p>If we paste the <code>if</code> into <code>tick()</code>, we have the same three lines in two places.</p>
<p>Then the next time defeating an enemy needs to change, we have to remember both.</p>
<p>We have seen this before.</p>
<p>When the same enemy was being built in three different places, we gave the building a name, and <code>Enemy::for_stage()</code> became the one place that builds an enemy.</p>
<p>Same fix here.</p>
<p>Add a method inside the same <code>impl Game</code> block, under <code>tick()</code>:</p>
<pre><code class="language-rust">    fn resolve_hit(&amp;mut self) {
        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }
</code></pre>
<p>Read it out loud:</p>
<blockquote>
<p>A hit just landed. If the enemy went down, handle the defeat.</p>
</blockquote>
<p>Now both ways of landing a hit can finish the same way.</p>
<p>Replace the whole <code>click()</code> method with this one. The old <code>if</code> is gone, and a call to <code>resolve_hit()</code> takes its place:</p>
<pre><code class="language-rust">    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);
        self.resolve_hit();
    }
</code></pre>
<p>And replace <code>tick()</code> with this. Only the last line inside the loop is new:</p>
<pre><code class="language-rust">    fn tick(&amp;mut self, seconds: u32) {
        for _ in 0..seconds {
            let damage = idle_damage_for_level(self.player.level);

            self.enemy.health = health_after_hit(self.enemy.health, damage);
            self.resolve_hit();
        }
    }
</code></pre>
<p>Run the tests again:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>All eighteen pass, including the clicking tests we did not touch.</p>
<p>That is what a good extraction looks like.</p>
<p><code>click()</code> does the same thing it did before.</p>
<p>It just does it by calling a method that <code>tick()</code> can call too.</p>
<p>Now follow the path of the twentieth second:</p>
<pre><code class="language-text">tick()
    ↓
health_after_hit() → 0
    ↓
resolve_hit()
    ↓
defeat_enemy()
    ↓
gold, XP, next stage, next enemy
</code></pre>
<p>That bottom half is the same code a click reaches.</p>
<p>The slime can die from a click or from a second of waiting.</p>
<p>Either way, it dies the same way.</p>
<hr />
<h2>Test time without waiting</h2>
<p>Look at what we just tested.</p>
<p>Twenty seconds of game time ran, and the test finished instantly.</p>
<p>Nothing slept. Nothing waited.</p>
<p>The game does not know what a second is.</p>
<p>It was handed the number 20 and did twenty steps of work.</p>
<p>That is the payoff for the design.</p>
<p>Time is an input, so a test can supply as much of it as it likes.</p>
<p>Three more tests are worth adding. Put each one at the bottom of the <code>tests</code> module.</p>
<p>One second should be one hit:</p>
<pre><code class="language-rust">#[test]
fn one_second_is_one_idle_hit() {
    let mut game = Game::new();

    game.tick(1);

    assert_eq!(game.enemy.health, 19);
}
</code></pre>
<p>Zero seconds should change nothing:</p>
<pre><code class="language-rust">#[test]
fn zero_seconds_changes_nothing() {
    let mut game = Game::new();

    game.tick(0);

    assert_eq!(game.stage, 1);
    assert_eq!(game.enemy.health, 20);
}
</code></pre>
<p>We did not write any code to handle zero.</p>
<p>We did not need to.</p>
<p><code>0..0</code> is an empty range, so the loop runs zero times and the game stays exactly as it was.</p>
<p>The last test is the one that matters most.</p>
<p>Waiting forty-five seconds in one go should give the same game as waiting one second forty-five times:</p>
<pre><code class="language-rust">#[test]
fn one_long_tick_matches_many_short_ones() {
    let mut long = Game::new();
    let mut short = Game::new();

    long.tick(45);

    for _ in 0..45 {
        short.tick(1);
    }

    assert_eq!(long.stage, short.stage);
    assert_eq!(long.enemy.health, short.enemy.health);
    assert_eq!(long.player.gold, short.player.gold);
    assert_eq!(long.player.xp, short.player.xp);
    assert_eq!(long.player.level, short.player.level);
}
</code></pre>
<p>This one passes by construction today, because <code>tick(45)</code> literally is forty-five single steps.</p>
<p>Today it almost looks redundant. Later, when we replace thousands of tiny steps with arithmetic, it becomes the test that proves the optimization did not change the game.</p>
<p>It is a promise to our future selves.</p>
<p>Later, a browser will sometimes call <code>tick(1)</code> every second.</p>
<p>Other times, it will come back after a long absence and call <code>tick(7200)</code> once.</p>
<p>Those two have to be the same game.</p>
<p>If a change ever makes them different, this test will say so.</p>
<p>Run them all:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>Twenty-one tests should pass.</p>
<hr />
<h2>Leave for an hour</h2>
<p>Time to try it for real.</p>
<p>We are going to leave the loop in <code>main()</code> exactly as it is, with one change: stop at stage 3, so the output is short.</p>
<pre><code class="language-rust">    while game.stage &lt;= 3 {
</code></pre>
<p>Everything inside the loop stays the same.</p>
<p>Then, after the loop, the player leaves.</p>
<p>Add this after the closing brace of the <code>while</code> loop, still inside <code>main()</code>:</p>
<pre><code class="language-rust">    let stage = game.stage;
    let level = game.player.level;
    let gold = game.player.gold;

    game.tick(3600);

    println!("One hour away.");
    println!(
        "Stage {} -&gt; {}  Level {} -&gt; {}  Gold {} -&gt; {}",
        stage, game.stage, level, game.player.level, gold, game.player.gold
    );
</code></pre>
<p>Three thousand six hundred is the number of seconds in an hour.</p>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">Stage 1 cleared!  Gold: 5  XP: 6/20
  Next up: Training Slime with 30 health.
Stage 2 cleared!  Gold: 12  XP: 14/20
  Next up: Training Slime with 40 health.
Bought click upgrade 1! Click damage: 7  Gold left: 2
Stage 3 cleared!  Gold: 11  XP: 4/35
Level up! Now level 2. Click damage: 8
  Next up: Training Slime with 50 health.
One hour away.
Stage 4 -&gt; 131  Level 2 -&gt; 48  Gold 11 -&gt; 17410
</code></pre>
<p>The first half is the player, clicking their way through three stages.</p>
<p>The last two lines are the hour.</p>
<p>One hour, with nobody at the keyboard, took the game from stage 4 to stage 131.</p>
<p>The player also came home with 17,410 gold and nothing spent. Noted.</p>
<p>Look at the level.</p>
<p>It went from 2 to 48.</p>
<p>That was not a bug.</p>
<p>Every trip through the <code>for</code> loop asks for the damage at the player's <em>current</em> level.</p>
<p>When a slime pays out enough XP, the player levels up in the middle of the hour, and every second after that hits a little harder.</p>
<p>Waiting makes the player stronger, which makes waiting faster.</p>
<p>That is the shape of an idle game.</p>
<p>Is it balanced? No.</p>
<p>A stage 131 slime after an hour is a lot.</p>
<p>We will tune that later. We have a rule with a name and a test, so tuning it is changing one function.</p>
<p>What matters today is that the engine can answer a question it could not answer before:</p>
<blockquote>
<p>What happened while I was away?</p>
</blockquote>
<hr />
<h2>Who owns the clock?</h2>
<p>The game never asked what time it was.</p>
<p>It was handed 3600, and it did 3600 seconds of work.</p>
<p>That is going to become one of the most important lines in this series:</p>
<pre><code class="language-text">Rust owns the rules.
Whatever runs the game owns time.
</code></pre>
<p>Today, that caller is a test or <code>main()</code>. Later, it will be a web page.</p>
<p>The caller measures how much real time passed and hands that fact to the game.</p>
<p>Rust decides what that time means for gameplay.</p>
<p>That distinction matters. A browser might report that 28,800 seconds passed while the player was away. The game can still decide that only part of that time should count.</p>
<p>In other words:</p>
<pre><code class="language-text">caller measures elapsed time
        ↓
Rust receives the number
        ↓
Rust decides how much time counts
        ↓
Rust applies the game rules
</code></pre>
<p>That keeps balance decisions inside the game instead of hiding them in whichever environment happens to run it.</p>
<p>There is one honest cost.</p>
<p><code>tick()</code> does one trip through the loop for every second.</p>
<p>An hour is 3,600 trips. A day is 86,400.</p>
<p>That is still manageable for this tiny program, but the cost grows directly with the number of seconds we simulate.</p>
<p>A number like <code>u32::MAX</code> would be a very long walk.</p>
<p>Later, we can cap credited away-time or replace repeated steps with arithmetic. The caller will still report elapsed time; Rust will still own the policy and the rules.</p>
<hr />
<h2>The complete program</h2>
<p>If you are only reading along, skip to the last two sections.</p>
<p>If you have been following along in your editor, here is everything in one place.</p>
<p>At this point, <code>src/main.rs</code> should look like this:</p>
<pre><code class="language-rust">struct Player {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}

struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

#[derive(Debug, PartialEq)]
enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}

impl Player {
    fn new() -&gt; Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }

    fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }

    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }

    fn bonus_damage(&amp;self) -&gt; u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }

    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold &lt; cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
}

impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);
        self.resolve_hit();
    }

    fn tick(&amp;mut self, seconds: u32) {
        for _ in 0..seconds {
            let damage = idle_damage_for_level(self.player.level);

            self.enemy.health = health_after_hit(self.enemy.health, damage);
            self.resolve_hit();
        }
    }

    fn resolve_hit(&amp;mut self) {
        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }

    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}

fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}

fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}

fn idle_damage_for_level(level: u32) -&gt; u32 {
    level
}

fn click_upgrade_bonus(upgrades: u32) -&gt; u32 {
    upgrades.saturating_mul(2)
}

fn click_upgrade_cost(upgrades: u32) -&gt; u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage());

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 3 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage())
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }

        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                click_damage(game.player.base_damage(), game.player.bonus_damage()),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold =&gt; {}
        }
    }

    let stage = game.stage;
    let level = game.player.level;
    let gold = game.player.gold;

    game.tick(3600);

    println!("One hour away.");
    println!(
        "Stage {} -&gt; {}  Level {} -&gt; {}  Gold {} -&gt; {}",
        stage, game.stage, level, game.player.level, gold, game.player.gold
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 5);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }

    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::NotEnoughGold);
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased);
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 15);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }

    #[test]
    fn idle_damage_grows_with_level() {
        assert_eq!(idle_damage_for_level(1), 1);
        assert_eq!(idle_damage_for_level(2), 2);
    }

    #[test]
    fn enough_time_defeats_the_enemy() {
        let mut game = Game::new();

        game.tick(20);

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }

    #[test]
    fn one_second_is_one_idle_hit() {
        let mut game = Game::new();

        game.tick(1);

        assert_eq!(game.enemy.health, 19);
    }

    #[test]
    fn zero_seconds_changes_nothing() {
        let mut game = Game::new();

        game.tick(0);

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
    }

    #[test]
    fn one_long_tick_matches_many_short_ones() {
        let mut long = Game::new();
        let mut short = Game::new();

        long.tick(45);

        for _ in 0..45 {
            short.tick(1);
        }

        assert_eq!(long.stage, short.stage);
        assert_eq!(long.enemy.health, short.enemy.health);
        assert_eq!(long.player.gold, short.player.gold);
        assert_eq!(long.player.xp, short.player.xp);
        assert_eq!(long.player.level, short.player.level);
    }
}
</code></pre>
<p>Run both:</p>
<pre><code class="language-bash">cargo run
cargo test
</code></pre>
<p>The game should clear three stages, buy one upgrade, spend an hour away, and all twenty-one tests should pass.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li>time as a plain number, passed in by the caller</li>
<li>the <code>tick</code> method</li>
<li><code>for</code> loops and ranges like <code>0..seconds</code></li>
<li><code>_</code> for a value we do not need</li>
<li>extracting a shared method so two paths finish the same way</li>
<li>tests that run an hour without waiting</li>
</ul>
<p>Two ideas are worth keeping.</p>
<p>The first is how to choose between the two loops we now know.</p>
<p>Ask whether you know the count.</p>
<p>The level-up loop could not know how many levels one reward would cross, so it was a <code>while</code>.</p>
<p><code>tick()</code> knows exactly how many seconds it was given, so it is a <code>for</code>.</p>
<p>The second is <code>resolve_hit()</code>.</p>
<p>The bug was not in the loop. The loop did what we told it.</p>
<p>The bug was that two ways of hurting an enemy had to finish the same way, and only one of them did.</p>
<p>Giving the finish a name fixed it, and now there is one place where "the enemy went down" turns into gold, XP, and a new slime.</p>
<hr />
<h2>You accidentally built a game engine</h2>
<p>Open <code>src/main.rs</code> and scroll.</p>
<p>Three structs. An enum. Three <code>impl</code> blocks. Twelve small rules. A click, a purchase, a clock that is not a clock, and twenty-one tests.</p>
<p>All of it is in one file, and the file is getting long.</p>
<p>Nobody planned that.</p>
<p>Each article needed one more thing, and each thing went in the next free spot.</p>
<p>But look at what is in there.</p>
<p>Everything above <code>main()</code> knows nothing about a terminal, a screen, or a clock.</p>
<p>Only <code>main()</code> does.</p>
<p>Today they share a file. Next time, we put a wall between them, and find out that the part on the far side of the wall is already a small game engine.</p>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[ `false` Is Not an Answer.]]></title><description><![CDATA[Learn Rust enums by building click upgrades. Replace a vague true or false with a PurchaseOutcome enum, then use match to handle every result.]]></description><link>https://journeytocode.hashnode.dev/rust-enums-and-match-click-upgrades</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/rust-enums-and-match-click-upgrades</guid><category><![CDATA[rust lang]]></category><category><![CDATA[Rust programming]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[beginners guide]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Wed, 21 Oct 2026 04:15:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/c779c721-7548-4975-9217-d3f673016d21.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our player earns gold now. After eight stages, they have ninety-six of it and nothing to spend it on.</p>
<p>That is not a reward. That is a high score.</p>
<p>So today we are going to sell them something: more damage per click, at a price that goes up every time they buy it.</p>
<p>The shopping is the easy part.</p>
<p>The interesting part is what happens when we try to tell the rest of the program whether the purchase worked.</p>
<p>Because the obvious answer, <code>true</code> or <code>false</code>, turns out to be not quite an answer at all.</p>
<pre><code class="language-text">Bought click upgrade 1! Click damage: 7  Gold left: 2
</code></pre>
<hr />
<h2>Our starting point</h2>
<p>If you are only reading along, you can skip past this code to "The free upgrade nobody earned."</p>
<p>If you are building along, you need three structs, three <code>impl</code> blocks, nine small functions, and thirteen tests:</p>
<pre><code class="language-rust">struct Player {
    bonus_damage: u32,
    gold: u32,
    xp: u32,
    level: u32,
}

struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

impl Player {
    fn new() -&gt; Player {
        Player {
            bonus_damage: 2,
            gold: 0,
            xp: 0,
            level: 1,
        }
    }

    fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }

    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }
}

impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);

        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}

fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}

fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage);

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 8 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage)
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 3);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 13);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }
}
</code></pre>
<p><code>Player</code> stores what the player has earned: gold, XP, and a level. Defeating an enemy pays gold and XP, and enough XP levels the player up.</p>
<p>Click damage comes from two places. <code>base_damage()</code> works it out from the player's level. <code>bonus_damage</code> is just a number stored in the struct.</p>
<p>Run <code>cargo test</code> and all thirteen should pass.</p>
<hr />
<h2>The free upgrade nobody earned</h2>
<p>Look at that <code>Player</code> again.</p>
<pre><code class="language-rust">    bonus_damage: u32,
</code></pre>
<p>Every new player starts with 2 of it. For free. Since the first time we gave the player a second damage number.</p>
<p><code>base_damage</code> is already a method, not a field. It is worked out from the player's level, because it is a consequence of that level, not a fact to store.</p>
<p><code>bonus_damage</code> has the same problem, with a different cause.</p>
<p>Bonus damage should be a consequence of how many upgrades the player has bought.</p>
<p>So the player does not need to store the bonus. The player needs to store the shopping.</p>
<p>Two rules, under <code>base_damage_for_level()</code>:</p>
<pre><code class="language-rust">fn click_upgrade_bonus(upgrades: u32) -&gt; u32 {
    upgrades.saturating_mul(2)
}

fn click_upgrade_cost(upgrades: u32) -&gt; u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}
</code></pre>
<p>Each upgrade adds 2 damage:</p>
<pre><code class="language-text">0 upgrades → 0 bonus
1 upgrade  → 2 bonus
2 upgrades → 4 bonus
</code></pre>
<p>And each one costs more than the last:</p>
<pre><code class="language-text">first upgrade  → 10 gold
second upgrade → 20 gold
third upgrade  → 30 gold
</code></pre>
<p>Both rules take the number of upgrades already bought. Neither one knows a <code>Player</code> exists.</p>
<p>Now replace the field with a count of purchases:</p>
<pre><code class="language-rust">struct Player {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}
</code></pre>
<p>A new player has bought nothing:</p>
<pre><code class="language-rust">            click_upgrade_level: 0,
</code></pre>
<p>And just like base damage, bonus damage becomes a method that asks the rule. Put it under <code>base_damage()</code>:</p>
<pre><code class="language-rust">    fn bonus_damage(&amp;self) -&gt; u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }
</code></pre>
<p><code>attack()</code> gets its second pair of parentheses:</p>
<pre><code class="language-rust">    let damage = click_damage(player.base_damage(), player.bonus_damage());
</code></pre>
<p>Notice what <code>Player</code> is made of now.</p>
<pre><code class="language-text">Player
├── gold
├── xp
├── level
└── click_upgrade_level
</code></pre>
<p>Not a single damage number.</p>
<p>Every field is a record of <strong>progress</strong>: what the player has earned, and what they have done with it.</p>
<p>The damage numbers are worked out from that progress, by rules, on demand.</p>
<p>That is going to matter a great deal in a few articles, when we save the game.</p>
<p>We will only have to save what the player earned. Everything else can be recalculated.</p>
<h3>The first fight changes</h3>
<p>One honest side effect.</p>
<p>A new player used to hit for 7: 5 base plus 2 free bonus.</p>
<p>Now they hit for 5, because the bonus has to be bought.</p>
<p>The stage 1 slime still has 20 health, so it takes four clicks instead of three.</p>
<p>Two of our tests notice.</p>
<p><code>clicking_damages_the_enemy</code> expected 13 health after one click. It is 15 now.</p>
<p><code>attack_reduces_enemy_health</code> expected 3 health left from 10. It is 5.</p>
<p>Update both numbers and run <code>cargo test</code>.</p>
<p>Those tests were not broken. The game changed, and the tests were telling the truth about it.</p>
<hr />
<h2>Buying the upgrade</h2>
<p>Here is the first version of the purchase. Add it to <code>impl Player</code>, under <code>bonus_damage()</code>:</p>
<pre><code class="language-rust">    fn buy_click_upgrade(&amp;mut self) -&gt; bool {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold &lt; cost {
            return false;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        true
    }
</code></pre>
<p>Work out the price.</p>
<p>If the player cannot afford it, stop, and say so.</p>
<p>Otherwise, take the money, add the upgrade, and say so.</p>
<p>Two things to notice.</p>
<p><code>return false;</code> leaves the method early.</p>
<p>Everything after it only runs if the player could afford the upgrade.</p>
<p>That lets the rest of the method read without an <code>else</code> wrapped around it.</p>
<p>And <code>self.gold -= cost;</code> is a plain minus sign, like the one in <code>add_xp()</code>.</p>
<p>The check right above it guarantees <code>self.gold</code> is at least <code>cost</code>, so the subtraction cannot go below zero.</p>
<p>Then <code>Game</code> passes the request along, inside <code>impl Game</code>:</p>
<pre><code class="language-rust">    fn buy_click_upgrade(&amp;mut self) -&gt; bool {
        self.player.buy_click_upgrade()
    }
</code></pre>
<p>The game does not know the price of an upgrade.</p>
<p>It does not know what happens when you buy one.</p>
<p>It knows the player handles that.</p>
<p>Nothing calls either method yet, so Rust will warn about both. <code>main()</code> will fix that shortly.</p>
<hr />
<h2><code>true</code> is not an answer</h2>
<p>This works.</p>
<p>Now imagine using it:</p>
<pre><code class="language-rust">if game.buy_click_upgrade() {
    println!("Bought it!");
} else {
    println!("???");
}
</code></pre>
<p>What goes in the <code>else</code>?</p>
<p><code>false</code> means the purchase did not happen.</p>
<p>It does not say <em>why</em>.</p>
<p>Today there is only one reason: not enough gold.</p>
<p>But look at this from the other side.</p>
<p>The code calling <code>buy_click_upgrade()</code> has to <em>remember</em> that <code>false</code> means "not enough gold."</p>
<p>Nothing in the code says so.</p>
<p>Add a second reason to fail someday, say a maximum upgrade level, and <code>false</code> starts meaning two different things.</p>
<p>Every place that checked it is now quietly wrong, and Rust will not say a word.</p>
<p>We want an answer that says what happened in plain words, and that Rust can check for us.</p>
<hr />
<h2>Give the outcome a name</h2>
<p>Rust has a type for "one of these specific things":</p>
<pre><code class="language-rust">#[derive(Debug, PartialEq)]
enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}
</code></pre>
<p>Put it with the structs. It is a type, like they are.</p>
<p><code>enum</code> is short for <em>enumeration</em>: a type whose values are listed out, one by one.</p>
<p>A <code>PurchaseOutcome</code> is either <code>Purchased</code> or <code>NotEnoughGold</code>.</p>
<p>It cannot be anything else.</p>
<p>It cannot be both.</p>
<p>The two options are called <strong>variants</strong>, and you name one the same way you reach into a type for a function:</p>
<pre><code class="language-rust">PurchaseOutcome::NotEnoughGold
</code></pre>
<p>You can think about <code>bool</code> the same way for this purpose: it also stands for one of exactly two possibilities, <code>true</code> or <code>false</code>.</p>
<p>The difference is that our enum lets us give those possibilities names that belong to our game.</p>
<h3>The line above the enum</h3>
<pre><code class="language-rust">#[derive(Debug, PartialEq)]
</code></pre>
<p><code>#[derive(...)]</code> asks Rust to generate implementations for the traits we list. Here, that saves us from writing the standard <code>Debug</code> and <code>PartialEq</code> implementations by hand.</p>
<p><code>PartialEq</code> is what lets <code>==</code> compare two outcomes. Without it, Rust has no idea whether <code>Purchased == Purchased</code>.</p>
<p><code>Debug</code> is what lets a failing test print the outcome it got, so the failure message can say <code>NotEnoughGold</code> instead of nothing at all.</p>
<p>Our tests will want both.</p>
<h3>Use it</h3>
<p>Now the purchase can say what happened:</p>
<pre><code class="language-rust">    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold &lt; cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
</code></pre>
<p>Same shape. Same early return.</p>
<p>Only the answers changed, and now they read like the game talking.</p>
<p><code>Game::buy_click_upgrade()</code> changes its return type to match:</p>
<pre><code class="language-rust">    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
</code></pre>
<hr />
<h2><code>match</code>: one branch per answer</h2>
<p>Rust's <code>match</code> expression gives us a way to handle each possible variant explicitly.</p>
<p>Here is how <code>main()</code> will react to a purchase:</p>
<pre><code class="language-rust">match game.buy_click_upgrade() {
    PurchaseOutcome::Purchased =&gt; println!("Bought it!"),
    PurchaseOutcome::NotEnoughGold =&gt; {}
}
</code></pre>
<p><code>match</code> looks at the value and runs the branch that fits.</p>
<p>Each line is a pattern, an arrow, and what to do.</p>
<p>The <code>{}</code> on the second branch means: do nothing.</p>
<p>That is allowed. What is not allowed is leaving a variant out.</p>
<p>Delete the <code>NotEnoughGold</code> line and Rust refuses to compile:</p>
<pre><code class="language-text">error[E0004]: non-exhaustive patterns:
`PurchaseOutcome::NotEnoughGold` not covered
</code></pre>
<p>This is the payoff.</p>
<p>With our <code>bool</code> version, the caller had to remember what <code>false</code> meant.</p>
<p>With an enum and <code>match</code>, the possible outcomes are named, and Rust can make us handle every one.</p>
<p>If we add a third variant next month, every <code>match</code> in the program will stop compiling until it says what to do about it.</p>
<p>Rust is keeping a list for us.</p>
<hr />
<h2><code>main()</code> goes shopping</h2>
<p>Add the purchase to the loop, after the stage-cleared block:</p>
<pre><code class="language-rust">        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                click_damage(game.player.base_damage(), game.player.bonus_damage()),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold =&gt; {}
        }
</code></pre>
<p>Every turn, <code>main()</code> tries to buy an upgrade, and only says something when it works.</p>
<p>Worth being clear about what that means: in this terminal version, the game buys an upgrade the moment the player can afford one. Nobody is deciding anything.</p>
<p>Later, when this game has a button, the player will choose when to spend. The purchasing rule does not change when that happens. Only who calls it.</p>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<pre><code class="language-text">Stage 1 cleared!  Gold: 5  XP: 6/20
  Next up: Training Slime with 30 health.
Stage 2 cleared!  Gold: 12  XP: 14/20
  Next up: Training Slime with 40 health.
Bought click upgrade 1! Click damage: 7  Gold left: 2
Stage 3 cleared!  Gold: 11  XP: 4/35
Level up! Now level 2. Click damage: 8
  Next up: Training Slime with 50 health.
Stage 4 cleared!  Gold: 22  XP: 16/35
  Next up: Training Slime with 60 health.
Bought click upgrade 2! Click damage: 10  Gold left: 2
Stage 5 cleared!  Gold: 15  XP: 30/35
  Next up: Training Slime with 70 health.
Stage 6 cleared!  Gold: 30  XP: 11/50
Level up! Now level 3. Click damage: 11
  Next up: Training Slime with 80 health.
Bought click upgrade 3! Click damage: 13  Gold left: 0
Stage 7 cleared!  Gold: 17  XP: 29/50
  Next up: Training Slime with 90 health.
Stage 8 cleared!  Gold: 36  XP: 49/50
  Next up: Training Slime with 100 health.
</code></pre>
<p>Now gold goes somewhere.</p>
<p>It climbs, and then it drops to almost nothing, because something got bought.</p>
<p>Click damage goes 5 → 7 → 8 → 10 → 11 → 13, pushed up from two directions at once: levels the player earns, and upgrades the player buys.</p>
<p>Compare the last line to where we started this article. Ninety-six gold and nothing to do with it, versus thirty-six gold and three upgrades already paid for.</p>
<hr />
<h2>Testing the purchase</h2>
<p>The pricing rule gets a test for its numbers:</p>
<pre><code class="language-rust">    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }
</code></pre>
<p>Then the two things that can happen.</p>
<p>A broke player cannot buy anything, and a failed purchase changes nothing:</p>
<pre><code class="language-rust">    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::NotEnoughGold);
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased);
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }
</code></pre>
<p>Those <code>assert_eq!</code> lines comparing outcomes are exactly why we asked for <code>PartialEq</code> and <code>Debug</code>.</p>
<p>Without <code>PartialEq</code>, the comparison would not compile. Without <code>Debug</code>, a failure could not tell you which outcome it got instead.</p>
<p>Run them:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>All sixteen should pass.</p>
<hr />
<h2>The complete program</h2>
<p>If you are only reading along, skip to the last two sections.</p>
<p>If you have been following along in your editor, here is everything in one place.</p>
<p>At this point, <code>src/main.rs</code> should look like this:</p>
<pre><code class="language-rust">struct Player {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}

struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

#[derive(Debug, PartialEq)]
enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}

impl Player {
    fn new() -&gt; Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }

    fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }

    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }

    fn bonus_damage(&amp;self) -&gt; u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }

    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold &lt; cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
}

impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);

        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }

    fn buy_click_upgrade(&amp;mut self) -&gt; PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}

fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}

fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}

fn click_upgrade_bonus(upgrades: u32) -&gt; u32 {
    upgrades.saturating_mul(2)
}

fn click_upgrade_cost(upgrades: u32) -&gt; u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage());

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 8 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage())
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }

        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased =&gt; println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                click_damage(game.player.base_damage(), game.player.bonus_damage()),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold =&gt; {}
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 5);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }

    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::NotEnoughGold);
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased);
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 15);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }
}
</code></pre>
<p>Run both:</p>
<pre><code class="language-bash">cargo run
cargo test
</code></pre>
<p>The game should clear eight stages, buying three upgrades along the way, and all sixteen tests should pass.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li>a second rule-derived value, replacing a stored one</li>
<li>early <code>return</code></li>
<li><code>enum</code> and variants</li>
<li><code>#[derive(Debug, PartialEq)]</code></li>
<li><code>match</code>, and the compiler refusing to let us forget a case</li>
</ul>
<p>Two ideas are worth keeping.</p>
<p>The first finishes the job <code>base_damage()</code> started. <code>Player</code> has no damage numbers in it at all now. It stores progress, and damage is a rule applied to that progress.</p>
<p>The second is <code>PurchaseOutcome</code>.</p>
<p>When a <code>bool</code> would have made the caller remember what <code>false</code> meant, we gave the answers names instead, and Rust started checking that every answer gets handled.</p>
<p>That pattern shows up everywhere in Rust. Two common examples you will eventually meet are <code>Option</code>, for a value that might not be there, and <code>Result</code>, for an operation that might have failed.</p>
<p>This is the first time we needed our own. It will not be the last.</p>
<hr />
<h2>Nothing happens unless you click</h2>
<p>Our game finally has a progression loop.</p>
<p>Kill a slime, get paid, level up, buy damage, kill a tougher slime.</p>
<p>There is just one problem with calling it an idle game.</p>
<pre><code class="language-text">clicks per second while you are playing: a lot
clicks per second while you are not:    zero
</code></pre>
<p>Nothing happens unless you click.</p>
<p>Walk away, come back an hour later, and the slime is exactly where you left it.</p>
<p>An idle game is supposed to keep working while you do not.</p>
<p>Next time, we are going to give the game time.</p>
<p>Not a timer. Not a clock. Just a number: how many seconds went by.</p>
<p>The game will take that number and work out everything that should have happened.</p>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Winning Is Not Progress.]]></title><description><![CDATA[Learn Rust by making winning pay. Add gold, XP, and levels to an idle game, level up with a while loop, and calculate damage with a &self method.]]></description><link>https://journeytocode.hashnode.dev/add-gold-xp-and-levels-in-rust</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/add-gold-xp-and-levels-in-rust</guid><category><![CDATA[Rust]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Programming basics]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Mon, 19 Oct 2026 11:03:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/b77d8e37-5ac7-4062-97c3-b2cd9bf37e00.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our game can fight its way through stage after stage now.</p>
<p>Click. The slime falls. A tougher one oozes in. Click.</p>
<p>That is a loop.</p>
<p>It is also a treadmill.</p>
<p>Every slime gets stronger. Our player never does.</p>
<p>In this article, winning starts to pay: gold and experience for every kill, and levels that make the player hit harder.</p>
<p>By the end, clearing a stage prints this:</p>
<pre><code class="language-text">Stage 3 cleared!  Gold: 21  XP: 4/35
Level up! Now level 2. Click damage: 8
  Next up: Training Slime with 50 health.
</code></pre>
<hr />
<h2>Our starting point</h2>
<p>If you are only reading along, you can skip past this code to "Where does the gold go?"</p>
<p>If you are building along, you need three <code>struct</code>s, two <code>impl</code> blocks, five small functions, and eight tests:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
}

struct Enemy {
    name: String,
    health: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player {
                base_damage: 5,
                bonus_damage: 2,
            },
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);

        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    let damage = click_damage(player.base_damage, player.bonus_damage);

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 3 {
        let stage = game.stage;

        game.click();

        if game.stage &gt; stage {
            println!("Stage {stage} cleared!");
        } else {
            println!("{} health: {}", game.enemy.name, game.enemy.health);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player {
            base_damage: 5,
            bonus_damage: 2,
        };

        let mut enemy = Enemy {
            name: String::from("Training Slime"),
            health: 10,
        };

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 3);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 13);
    }

    #[test]
    fn defeating_an_enemy_advances_the_stage() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
    }
}
</code></pre>
<p><code>game.click()</code> attacks the enemy. If that attack kills it, <code>defeat_enemy()</code> advances the stage and sends in the next one.</p>
<p>The tests hold those rules in place. Three of them are about to change, so it helps to see them as they are now.</p>
<p>Everything today builds on that.</p>
<hr />
<h2>Where does the gold go?</h2>
<p>Defeating an enemy should pay.</p>
<p>Before we can pay the player, the player needs somewhere to put the money.</p>
<p>Right now, our <code>Player</code> has two fields, and neither of them is a wallet.</p>
<p>Add one:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
    gold: u32,
}
</code></pre>
<p>Rust immediately complains.</p>
<p>Every place we build a <code>Player</code> now has to say how much gold it starts with.</p>
<p>There are two of those: <code>Game::new()</code> and the <code>attack_reduces_enemy_health</code> test. (<code>cargo run</code> only finds the first one, because it does not compile the tests. <code>cargo test</code> finds both.)</p>
<p>We are about to change <code>Player</code> a few more times today, and we do not want to chase every field through every place a player gets built.</p>
<p><code>Enemy::for_stage()</code> already solves this exact problem for enemies.</p>
<p>Give <code>Player</code> the same treatment:</p>
<pre><code class="language-rust">impl Player {
    fn new() -&gt; Player {
        Player {
            base_damage: 5,
            bonus_damage: 2,
            gold: 0,
        }
    }
}
</code></pre>
<p>Put it right after the structs, next to <code>impl Enemy</code>.</p>
<p>Same convention as <code>Game::new()</code>: <code>new</code> is not a Rust keyword, just the name Rust programmers use for "make me a fresh one of these."</p>
<p>Then use it in <code>Game::new()</code>:</p>
<pre><code class="language-rust">            player: Player::new(),
</code></pre>
<p>and in the test:</p>
<pre><code class="language-rust">        let player = Player::new();
</code></pre>
<p>Now there is exactly one place that knows what a new player looks like.</p>
<p>When <code>Player</code> changes again, and it will, that is the only place we touch.</p>
<p>While you are in the tests, <code>a_new_game_starts_at_stage_one</code> can check that the wallet starts empty:</p>
<pre><code class="language-rust">    assert_eq!(game.player.gold, 0);
</code></pre>
<p>Run <code>cargo test</code>.</p>
<p>All eight should still pass.</p>
<p>If you <code>cargo run</code> instead, Rust warns that <code>gold</code> is never read. A warning, not an error. The game is about to read it.</p>
<hr />
<h2>How much is a slime worth?</h2>
<p>How much gold should a defeated enemy drop?</p>
<p>That is a game rule.</p>
<p>Game rules get their own small function, like <code>enemy_health_for_stage()</code>.</p>
<p>Give this one the same treatment, right next to it:</p>
<pre><code class="language-rust">fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}
</code></pre>
<pre><code class="language-text">stage 1 → 5 gold
stage 2 → 7 gold
stage 3 → 9 gold
</code></pre>
<p>Later enemies are tougher.</p>
<p>They should also pay better.</p>
<p>Now, where does that reward live?</p>
<p>It belongs to the enemy.</p>
<p>A slime knows how much health it has.</p>
<p>It should also know what it is worth:</p>
<pre><code class="language-rust">struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
}
</code></pre>
<p>And <code>Enemy::for_stage()</code> fills it in:</p>
<pre><code class="language-rust">impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
        }
    }
}
</code></pre>
<p>Because every enemy is built through <code>for_stage()</code>, this is the only place that needed to change.</p>
<p>Now run <code>cargo test</code> again.</p>
<p><code>cargo run</code> would build, with one more never-read warning, because the game only builds enemies through <code>for_stage()</code>.</p>
<p>The tests do not build. <code>attack_reduces_enemy_health</code> builds an <code>Enemy</code> by hand, with a name and 10 health, and Rust wants a <code>gold_reward</code> there too.</p>
<p>That is the compiler finding the last place for us.</p>
<p>Instead of making that test learn about rewards, let it start from a real enemy and adjust the one thing it cares about:</p>
<pre><code class="language-rust">        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;
</code></pre>
<p>The test still checks what it always checked: an attack takes health off the enemy.</p>
<hr />
<h2>Paying out</h2>
<p>The player has a wallet. The enemy has a price on its head.</p>
<p>Handing one to the other is the player's job.</p>
<p>Add a method to <code>impl Player</code>, under <code>new()</code>:</p>
<pre><code class="language-rust">    fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }
</code></pre>
<p><code>&amp;mut self</code> is the same <code>&amp;mut self</code> from <code>click()</code>.</p>
<p>The method borrows the player, changes it, and gives it back.</p>
<p>Which brings us to <code>defeat_enemy()</code>, the one place in the game that already knows an enemy just died.</p>
<p>It grows:</p>
<pre><code class="language-rust">    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }
</code></pre>
<p>The order matters.</p>
<p>We pay the player <strong>before</strong> the enemy is replaced.</p>
<p>On the next line, <code>self.enemy</code> becomes a brand new slime with a brand new reward.</p>
<p>If we paid afterward, the player would get paid for an enemy they have not fought yet.</p>
<p>Read <code>defeat_enemy()</code> out loud now:</p>
<blockquote>
<p>Pay the player. Go to the next stage. Bring in the next enemy.</p>
</blockquote>
<p>No arithmetic to decode.</p>
<hr />
<h2>Experience and levels</h2>
<p>Gold is something to spend.</p>
<p>Experience, or XP, is something to keep.</p>
<p>Every defeated enemy should also give the player experience, and when they have enough, they level up.</p>
<p>That is two rules, so we need two numbers:</p>
<pre><code class="language-text">how much XP does an enemy give?
how much XP does the next level take?
</code></pre>
<p>The first one looks like the gold rule.</p>
<p>Put it right under <code>gold_reward_for_stage()</code>:</p>
<pre><code class="language-rust">fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}
</code></pre>
<p>The second one is new.</p>
<p>Each level should take a little more than the last:</p>
<pre><code class="language-rust">fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}
</code></pre>
<pre><code class="language-text">level 1 → 20 XP to reach level 2
level 2 → 35 XP to reach level 3
level 3 → 50 XP to reach level 4
</code></pre>
<p>That chain is split across lines so each step is easy to read. A line starting with <code>.</code> just continues the line above it.</p>
<p>Level 1 is the starting line. The <code>saturating_sub(1)</code> makes it count as zero steps taken, so level 1 needs the plain 20 and every level after that adds 15. Without it, the first level would already cost 35.</p>
<p>The exact numbers do not matter yet.</p>
<p>They have names and they will have tests.</p>
<p>The enemy carries its XP reward the same way it carries gold:</p>
<pre><code class="language-rust">struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}
</code></pre>
<p>Add it to <code>Enemy::for_stage()</code>, under <code>gold_reward</code>:</p>
<pre><code class="language-rust">            xp_reward: xp_reward_for_stage(stage),
</code></pre>
<p>And the player needs two more fields: how much XP they have, and what level they are:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
    gold: u32,
    xp: u32,
    level: u32,
}
</code></pre>
<p>In <code>Player::new()</code>:</p>
<pre><code class="language-rust">            xp: 0,
            level: 1,
</code></pre>
<p>That is now the normal place a fresh player is built, so future changes stay concentrated there.</p>
<p>Run <code>cargo test</code>. Still eight, plus a few warnings that the new fields and <code>xp_to_next_level()</code> are never used. The next section uses them.</p>
<hr />
<h2>Leveling up</h2>
<p>Now the interesting part.</p>
<p>Adding XP is not like adding gold.</p>
<p>Gold just piles up.</p>
<p>XP piles up until it crosses a line, and then something happens:</p>
<pre><code class="language-rust">    fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }
</code></pre>
<p>Add it to <code>impl Player</code>, right under <code>add_gold()</code>.</p>
<p>Walk through it with a level 1 player who has 0 XP and just earned 25.</p>
<p>The XP becomes 25.</p>
<p>Level 1 needs 20. We have 25. That is enough, so we go into the loop.</p>
<p>Spend 20 of it. XP is now 5. Level is now 2.</p>
<p>Level 2 needs 35. We have 5. The loop stops.</p>
<p>The player is level 2 with 5 XP toward the next one.</p>
<p>Why a <code>while</code> and not an <code>if</code>?</p>
<p>Because a reward might be big enough to cross two lines at once.</p>
<p>Give that same level 1 player 60 XP.</p>
<p>Spend 20 → level 2, 40 left.</p>
<p>Level 2 needs 35. 40 is enough. Spend 35 → level 3, 5 left.</p>
<p>Level 3 needs 50. The loop stops.</p>
<p>An <code>if</code> would have stopped at level 2 and left 40 XP sitting there, more than enough for another level the player never got.</p>
<p>A <code>while</code> keeps going until the XP really is below the line.</p>
<p>You might notice that <code>xp_to_next_level(self.level)</code> gets called twice in a row. That is fine. It is a tiny calculation, and for the same level it always gives the same answer.</p>
<p>That matters more than it looks right now. Later in this series, a player is going to come back after a few hours away and collect a very large pile of XP all at once.</p>
<h3>A plain minus sign</h3>
<p>Look at this line:</p>
<pre><code class="language-rust">self.xp -= xp_to_next_level(self.level);
</code></pre>
<p><code>-=</code> is shorthand: <code>self.xp -= 20</code> means <code>self.xp = self.xp - 20</code>.</p>
<p>That is ordinary subtraction, not <code>saturating_sub()</code>.</p>
<p>Everywhere else in this program, subtraction goes through <code>saturating_sub()</code>.</p>
<p>Here, it is safe, and the line right above it is the reason.</p>
<p>The <code>while</code> only lets us in when <code>self.xp</code> is at least as big as the amount we are about to subtract.</p>
<p>The result can never drop below zero.</p>
<p><code>saturating_sub()</code> is for when a number <em>might</em> go somewhere it should not.</p>
<p>When the code right next to it proves that it cannot, plain subtraction says exactly what it means.</p>
<p>Now <code>defeat_enemy()</code> pays both:</p>
<pre><code class="language-rust">        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);
</code></pre>
<hr />
<h2>What does a level actually do?</h2>
<p>Right now, nothing.</p>
<p>The player levels up, and hits exactly as hard as before.</p>
<p>A level should make the player stronger.</p>
<p>Look at <code>Player</code> again:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
    gold: u32,
    xp: u32,
    level: u32,
}
</code></pre>
<p><code>base_damage</code> is a number we wrote down once, when the player was created, and never touched again.</p>
<p>But base damage should not be a fact about the player anymore.</p>
<p>It should be a <em>consequence</em> of the player's level.</p>
<p>That makes it a rule. Put it under <code>xp_to_next_level()</code>:</p>
<pre><code class="language-rust">fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}
</code></pre>
<pre><code class="language-text">level 1 → 5 damage
level 2 → 6 damage
level 3 → 7 damage
</code></pre>
<p>Level 1 gives 5, which is exactly the <code>base_damage</code> we have had since the beginning.</p>
<p>Nothing about the first fight changes.</p>
<p>Now the field can go.</p>
<p>Remove <code>base_damage</code> from the struct and from <code>Player::new()</code>.</p>
<p>Rust will complain about one more place that still uses it. We will get to that in a second.</p>
<p>In its place, give <code>Player</code> a method that asks the rule, right under <code>add_xp()</code>:</p>
<pre><code class="language-rust">    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }
</code></pre>
<p>There is something new in that signature.</p>
<p><code>&amp;self</code>, without the <code>mut</code>.</p>
<p>So far, our methods have needed <code>&amp;mut self</code> because they changed something.</p>
<p>A method that only needs to <strong>read</strong> takes <code>&amp;self</code> instead. This is our first one.</p>
<p><code>base_damage()</code> looks at the player's level and reports a number.</p>
<p>It does not change anything, so it does not ask for permission to.</p>
<p>One thing still uses the old field: <code>attack()</code>.</p>
<p>It reads <code>player.base_damage</code>, and that field no longer exists.</p>
<p>Rust says so (the line is wrapped here to fit):</p>
<pre><code class="language-text">error[E0615]: attempted to take value of method
`base_damage` on type `&amp;Player`
</code></pre>
<p>and even suggests the fix: use parentheses to call the method.</p>
<pre><code class="language-rust">fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage);

    enemy.health = health_after_hit(enemy.health, damage);
}
</code></pre>
<p>The parentheses are the only difference.</p>
<p><code>player.base_damage</code> was a number stored in the player.</p>
<p><code>player.base_damage()</code> is a number the player works out from its level.</p>
<p><code>attack()</code> did not have to learn anything about levels.</p>
<p>It asked for base damage. It got base damage.</p>
<p>Run <code>cargo test</code>. All eight still pass, and that is the point: a level 1 player still deals 5 base damage, so nothing a test checks has changed.</p>
<hr />
<h2><code>main()</code> shows the progress</h2>
<p>Our <code>main()</code> has been printing the enemy's health after every click.</p>
<p>That was useful when a fight took three clicks. Now there are rewards to watch, so let us print when something happens instead:</p>
<pre><code class="language-rust">fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 8 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage)
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }
    }
}
</code></pre>
<p>Before each click, <code>main()</code> remembers the stage and the level.</p>
<p>After the click, it compares.</p>
<p>Stage went up? The enemy died, so say so, show the wallet, and introduce the next one.</p>
<p>Level went up? Say that too.</p>
<p>Notice that the level check sits <em>inside</em> the stage check. It can, because XP is only ever handed out in <code>defeat_enemy()</code>. No enemy died, no XP, no level up.</p>
<p>One small thing about that first <code>println!</code>. <code>{stage}</code> works because <code>stage</code> is a plain variable. <code>game.player.gold</code> has dots in it, so it goes in the list after the string and a bare <code>{}</code> picks it up.</p>
<p><code>main()</code> still does not know how leveling works.</p>
<p>It does borrow one rule, <code>xp_to_next_level()</code>, so it can show how far away the next level is. That is display, not a decision.</p>
<p>It clicks, it asks, and it prints what it was told.</p>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<pre><code class="language-text">Stage 1 cleared!  Gold: 5  XP: 6/20
  Next up: Training Slime with 30 health.
Stage 2 cleared!  Gold: 12  XP: 14/20
  Next up: Training Slime with 40 health.
Stage 3 cleared!  Gold: 21  XP: 4/35
Level up! Now level 2. Click damage: 8
  Next up: Training Slime with 50 health.
Stage 4 cleared!  Gold: 32  XP: 16/35
  Next up: Training Slime with 60 health.
Stage 5 cleared!  Gold: 45  XP: 30/35
  Next up: Training Slime with 70 health.
Stage 6 cleared!  Gold: 60  XP: 11/50
Level up! Now level 3. Click damage: 9
  Next up: Training Slime with 80 health.
Stage 7 cleared!  Gold: 77  XP: 29/50
  Next up: Training Slime with 90 health.
Stage 8 cleared!  Gold: 96  XP: 49/50
  Next up: Training Slime with 100 health.
</code></pre>
<p>Read that top to bottom.</p>
<p>Gold climbs. XP climbs toward a line, crosses it, and resets with the leftover carried over. Click damage starts at 7 and climbs to 8, then 9.</p>
<p>Compare that to where we started this article: 5 base damage plus 2 bonus, on stage 1 and on stage 30 alike.</p>
<p>The slimes still get tougher every stage. The difference is that now, so does the player.</p>
<hr />
<h2>Testing the rules</h2>
<p>Each new rule gets a test for its numbers. Add these to the <code>tests</code> module:</p>
<pre><code class="language-rust">    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }
</code></pre>
<p>Then the behaviors.</p>
<p>The two walkthroughs from <code>add_xp()</code> become tests, so the explanation and the code can never drift apart:</p>
<pre><code class="language-rust">    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }
</code></pre>
<p>And the big one.</p>
<p>The last test in our starting point, <code>defeating_an_enemy_advances_the_stage</code>, checked that a win moved the game forward.</p>
<p>Now a win should also pay.</p>
<p>Rename it and add two checks at the end:</p>
<pre><code class="language-rust">    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }
</code></pre>
<p>This test does not care how damage is calculated, or how XP crosses a threshold.</p>
<p>Those rules have their own tests now.</p>
<p>It asks the bigger question:</p>
<blockquote>
<p>When a click kills something, does the player get paid for it?</p>
</blockquote>
<p>Run them:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>All thirteen should pass.</p>
<hr />
<h2>The complete program</h2>
<p>If you are only reading along, skip to the last two sections.</p>
<p>If you have been following along in your editor, here is everything in one place.</p>
<p>At this point, <code>src/main.rs</code> should look like this:</p>
<pre><code class="language-rust">struct Player {
    bonus_damage: u32,
    gold: u32,
    xp: u32,
    level: u32,
}

struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

impl Player {
    fn new() -&gt; Player {
        Player {
            bonus_damage: 2,
            gold: 0,
            xp: 0,
            level: 1,
        }
    }

    fn add_gold(&amp;mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    fn add_xp(&amp;mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp &gt;= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }

    fn base_damage(&amp;self) -&gt; u32 {
        base_damage_for_level(self.level)
    }
}

impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);

        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn gold_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(3)
}

fn xp_reward_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(2).saturating_add(4)
}

fn xp_to_next_level(level: u32) -&gt; u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

fn base_damage_for_level(level: u32) -&gt; u32 {
    level.saturating_add(4)
}

fn attack(player: &amp;Player, enemy: &amp;mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage);

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 8 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage &gt; stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level &gt; level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage)
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 3);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 13);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }
}
</code></pre>
<p>Run both:</p>
<pre><code class="language-bash">cargo run
cargo test
</code></pre>
<p>The game should clear eight stages, and all thirteen tests should pass.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li><p><code>Player::new()</code>, so a player is built in one place</p>
</li>
<li><p><code>&amp;self</code> methods that read without changing</p>
</li>
<li><p>a <code>while</code> loop that keeps going until a condition stops being true</p>
</li>
<li><p><code>-=</code>, and plain subtraction where the code above it proves the number cannot go below zero</p>
</li>
<li><p>rules that calculate a value instead of storing it</p>
</li>
</ul>
<p>That is a short list, and the last item is the one worth keeping.</p>
<p><code>base_damage</code> used to be a number sitting in the player.</p>
<p>Now it is a rule applied to the player's level.</p>
<p>The player got smaller, and the game got bigger.</p>
<p>Hold onto that idea. We are going to do it again next time, to the other half of that damage calculation.</p>
<hr />
<h2>Gold with nothing to buy</h2>
<p>Our player finally gets stronger by winning.</p>
<p>There is just one problem.</p>
<p>Look at the last line of that run again:</p>
<pre><code class="language-text">Stage 8 cleared!  Gold: 96  XP: 49/50
</code></pre>
<p>Ninety-six gold.</p>
<p>We call it money, but there is nothing in this game to spend it on.</p>
<pre><code class="language-text">gold earned: 96
gold spent:  0
</code></pre>
<p>Right now it is not a reward.</p>
<p>It is a high score.</p>
<p>Next time, we are going to give the player something to buy, and discover that the obvious way to report whether a purchase worked is not good enough.</p>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[One Fight Is Not a Game.]]></title><description><![CDATA[Learn Rust impl blocks and methods by giving your idle game one Game struct, tougher enemies by stage, and a game.click() that runs a full turn.]]></description><link>https://journeytocode.hashnode.dev/rust-game-state-impl-methods</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/rust-game-state-impl-methods</guid><category><![CDATA[Rust]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sat, 17 Oct 2026 19:42:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/ed0ce1ab-4b47-409e-9f2b-2b95a565a072.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every fight in our game ends the same way.</p>
<p>The slime dies.</p>
<p>Then the program dies with it.</p>
<p>So far, winning has been how our game ends.</p>
<p>In an idle game, winning is how the game keeps going.</p>
<p>One enemy falls, and the next one walks in.</p>
<p>In this article, we are going to give our game a next stage, a tougher enemy waiting there, and one place to keep all of it.</p>
<p>By the end, a whole turn of our game will fit in one line:</p>
<pre><code class="language-rust">game.click();
</code></pre>
<p>That one call will attack the enemy, notice when it dies, advance the stage, and send in the next one.</p>
<hr />
<h2>Our starting point</h2>
<p>So far, we have a player, a slime, and a way for one to hit the other.</p>
<p>You need two structs and four small functions to follow along:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
}

struct Enemy {
    name: String,
    health: u32,
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn attack(
    player: &amp;Player,
    enemy: &amp;mut Enemy,
) {
    let damage = click_damage(
        player.base_damage,
        player.bonus_damage,
    );

    enemy.health =
        health_after_hit(enemy.health, damage);
}
</code></pre>
<p><code>attack()</code> borrows the player to read it and the enemy to change it.</p>
<p>Our combat loop looks like this:</p>
<pre><code class="language-rust">fn main() {
    let player = Player {
        base_damage: 5,
        bonus_damage: 2,
    };

    let mut enemy = Enemy {
        name: String::from("Training Slime"),
        health: 20,
    };

    while !is_defeated(enemy.health) {
        attack(&amp;player, &amp;mut enemy);

        println!(
            "{} health: {}",
            enemy.name,
            enemy.health
        );
    }

    println!("{} defeated!", enemy.name);
}
</code></pre>
<p>Run it and the Training Slime goes down in three hits.</p>
<p>Then the program is over.</p>
<hr />
<h2>The next enemy needs to be tougher</h2>
<p>Before we worry about where the next enemy comes from, we need to decide what it looks like.</p>
<p>If every enemy had 20 health, stage 50 would feel exactly like stage 1.</p>
<p>The next enemy should be tougher.</p>
<p>That is a game rule, and we know what to do with game rules by now.</p>
<p>Give it a small function:</p>
<pre><code class="language-rust">fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}
</code></pre>
<p>That gives us:</p>
<pre><code class="language-text">stage 1 → 20 health
stage 2 → 30 health
stage 3 → 40 health
</code></pre>
<p><code>saturating_mul()</code> is the multiplication version of the saturating arithmetic we already use.</p>
<p>The exact balance does not matter yet.</p>
<p>What matters is that the rule has a name and a test:</p>
<pre><code class="language-rust">#[test]
fn later_stages_have_tougher_enemies() {
    assert_eq!(enemy_health_for_stage(1), 20);
    assert_eq!(enemy_health_for_stage(2), 30);
}
</code></pre>
<p>If Rust warns that the function is never used yet, that is fine.</p>
<p>We are about to use it.</p>
<hr />
<h2>Try it in <code>main()</code> first</h2>
<p>The quickest way to make stages work is to put everything straight into <code>main()</code>:</p>
<pre><code class="language-rust">fn main() {
    let player = Player {
        base_damage: 5,
        bonus_damage: 2,
    };

    let mut stage = 1;

    let mut enemy = Enemy {
        name: String::from("Training Slime"),
        health: enemy_health_for_stage(stage),
    };

    while stage &lt;= 3 {
        attack(&amp;player, &amp;mut enemy);

        if is_defeated(enemy.health) {
            println!("Stage {stage} cleared!");

            stage = stage.saturating_add(1);

            enemy = Enemy {
                name: String::from("Training Slime"),
                health: enemy_health_for_stage(stage),
            };
        } else {
            println!(
                "{} health: {}",
                enemy.name,
                enemy.health
            );
        }
    }
}
</code></pre>
<p>We stop after stage 3 so the terminal does not scroll forever.</p>
<p>Run it.</p>
<p>It works.</p>
<p>The slime falls, a tougher one takes its place, and the stages keep coming.</p>
<p>It is also a mess.</p>
<p>Look at what <code>main()</code> is doing now:</p>
<pre><code class="language-text">owns the player
owns the enemy
owns the stage
decides when a stage is cleared
builds the next enemy
prints everything
</code></pre>
<p>Only that last job really belongs in <code>main()</code>.</p>
<p>Everything else is the game.</p>
<p>Soon, defeating an enemy will also hand out rewards.</p>
<p>If all of those rules stay here, <code>main()</code> becomes the game engine and the terminal UI at the same time.</p>
<p>There is another reason to fix that now.</p>
<p>Later, the browser will replace <code>main()</code>.</p>
<p>The game rules need somewhere else to live.</p>
<hr />
<h2>Give the game a shape</h2>
<p>We have solved this kind of problem before.</p>
<p>Three loose numbers were not a player, so we built a <code>Player</code>.</p>
<p>A stage, a player, and an enemy sitting loose in <code>main()</code> are not a game.</p>
<p>So we build a <code>Game</code>:</p>
<pre><code class="language-rust">struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}
</code></pre>
<p>This struct contains other structs:</p>
<pre><code class="language-text">Game
├── stage: u32
├── player: Player
└── enemy: Enemy
</code></pre>
<p>A struct can hold types we made ourselves.</p>
<p>The <code>Game</code> owns its <code>Player</code>.</p>
<p>It owns its <code>Enemy</code>.</p>
<p>There is now one value we can point at and say:</p>
<blockquote>
<p>This is the current state of the game.</p>
</blockquote>
<hr />
<h2>Where does a new enemy come from?</h2>
<p>We could create a game by spelling out every field manually:</p>
<pre><code class="language-rust">let mut game = Game {
    stage: 1,
    player: Player {
        base_damage: 5,
        bonus_damage: 2,
    },
    enemy: Enemy {
        name: String::from("Training Slime"),
        health: enemy_health_for_stage(1),
    },
};
</code></pre>
<p>That works.</p>
<p>But we have already written this enemy more than once:</p>
<pre><code class="language-rust">Enemy {
    name: String::from("Training Slime"),
    health: enemy_health_for_stage(stage),
}
</code></pre>
<p>What we really want to say is:</p>
<blockquote>
<p>Give me the enemy for this stage.</p>
</blockquote>
<p>That sounds like a function.</p>
<p>And because the function is specifically about creating an <code>Enemy</code>, it belongs with <code>Enemy</code>.</p>
<hr />
<h2>Attach functions to a type with <code>impl</code></h2>
<p>Rust lets us attach functions to a type with an <code>impl</code> block:</p>
<pre><code class="language-rust">impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
        }
    }
}
</code></pre>
<p><code>impl Enemy</code> means:</p>
<blockquote>
<p>here are functions that belong to <code>Enemy</code></p>
</blockquote>
<p>Now we can write:</p>
<pre><code class="language-rust">Enemy::for_stage(1)
</code></pre>
<p>You have already seen this style of call:</p>
<pre><code class="language-rust">String::from("Training Slime")
</code></pre>
<p>A function attached to a type that does not need an existing value is called an <strong>associated function</strong>.</p>
<p><code>Enemy::for_stage()</code> does not replace our small health rule.</p>
<p>It uses it:</p>
<pre><code class="language-rust">health: enemy_health_for_stage(stage),
</code></pre>
<p>One function decides how tough the enemy is.</p>
<p>The other decides how to build one.</p>
<hr />
<h2>A new game</h2>
<p><code>Game</code> gets an <code>impl</code> block too:</p>
<pre><code class="language-rust">impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player {
                base_damage: 5,
                bonus_damage: 2,
            },
            enemy: Enemy::for_stage(1),
        }
    }
}
</code></pre>
<p>There is nothing magical about <code>new</code>.</p>
<p>Rust has no special constructor keyword.</p>
<p><code>new</code> is simply a common name for an associated function that creates a fresh value.</p>
<p>Starting the game is now:</p>
<pre><code class="language-rust">let mut game = Game::new();
</code></pre>
<p>Game setup has one home.</p>
<p>And it is no longer <code>main()</code>.</p>
<hr />
<h2>One click</h2>
<p>Now the part we have been building toward.</p>
<p>What happens when the player clicks?</p>
<p>Add this inside <code>impl Game</code>:</p>
<pre><code class="language-rust">fn click(&amp;mut self) {
    attack(&amp;self.player, &amp;mut self.enemy);
}
</code></pre>
<p>There is one new name in that signature:</p>
<pre><code class="language-rust">self
</code></pre>
<p><code>self</code> means:</p>
<blockquote>
<p>the value this method was called on</p>
</blockquote>
<p>When we write:</p>
<pre><code class="language-rust">game.click();
</code></pre>
<p>inside <code>click()</code>, <code>self</code> refers to <code>game</code>.</p>
<p>That makes <code>click()</code> a <strong>method</strong>: a function attached to a type that works on a specific value of that type.</p>
<p><code>Game::new()</code> does not take <code>self</code> because there is no game yet.</p>
<p><code>click()</code> does because it works on a game that already exists.</p>
<p>And <code>&amp;mut self</code> should look familiar.</p>
<p>It means <code>click()</code> may temporarily change the game without taking ownership of it.</p>
<p>That is why this needs:</p>
<pre><code class="language-rust">let mut game = Game::new();
</code></pre>
<p>Without <code>mut</code>, Rust refuses to let <code>click()</code> borrow the game mutably.</p>
<hr />
<h2>Two borrows, one game</h2>
<p>Look inside <code>click()</code>:</p>
<pre><code class="language-rust">attack(&amp;self.player, &amp;mut self.enemy);
</code></pre>
<p>Last time, we learned:</p>
<blockquote>
<p><strong>For the same data at the same time: many readers, or one writer, but not both.</strong></p>
</blockquote>
<p>So why can we borrow part of the game to read and another part to change?</p>
<p>Because they are not the same data.</p>
<p><code>self.player</code> and <code>self.enemy</code> are different fields.</p>
<p>They do not overlap.</p>
<p>Rust can see that reading the player does not interfere with changing the enemy.</p>
<p>Our <code>attack()</code> did not need to change.</p>
<p>The borrowing simply moved inside the game.</p>
<hr />
<h2>What happens when the enemy dies?</h2>
<p>Right now, <code>click()</code> only attacks.</p>
<p>When the enemy reaches zero health, the next click would attack a corpse.</p>
<p>That is grim.</p>
<p>After every attack, <code>click()</code> should check whether the enemy was defeated:</p>
<pre><code class="language-rust">fn click(&amp;mut self) {
    attack(&amp;self.player, &amp;mut self.enemy);

    if is_defeated(self.enemy.health) {
        self.defeat_enemy();
    }
}
</code></pre>
<p>Now we need <code>defeat_enemy()</code>:</p>
<pre><code class="language-rust">fn defeat_enemy(&amp;mut self) {
    self.stage = self.stage.saturating_add(1);
    self.enemy = Enemy::for_stage(self.stage);
}
</code></pre>
<p>The stage goes up.</p>
<p>The defeated enemy is replaced with the enemy for the new stage.</p>
<p>We could put both lines directly inside <code>click()</code>.</p>
<p>But defeating an enemy is its own event, and that event is going to grow.</p>
<p>Soon it will also hand out rewards.</p>
<p>Keeping it named means <code>click()</code> can stay readable:</p>
<blockquote>
<p>Attack the enemy. If it went down, handle the defeat.</p>
</blockquote>
<p>And <code>defeat_enemy()</code> says:</p>
<blockquote>
<p>Go to the next stage. Bring in the next enemy.</p>
</blockquote>
<hr />
<h2><code>main()</code> gets boring</h2>
<p>Here is the new <code>main()</code>:</p>
<pre><code class="language-rust">fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 3 {
        let stage = game.stage;

        game.click();

        if game.stage &gt; stage {
            println!("Stage {stage} cleared!");
        } else {
            println!(
                "{} health: {}",
                game.enemy.name,
                game.enemy.health
            );
        }
    }
}
</code></pre>
<p><code>main()</code> still reads the game so it can decide what to print.</p>
<p>But it does not:</p>
<ul>
<li>build enemies</li>
<li>calculate their health</li>
<li>decide when a stage advances</li>
<li>handle a defeat</li>
</ul>
<p>It creates a game, clicks, and prints.</p>
<p>Boring is exactly what we want.</p>
<p>Run:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">Training Slime health: 13
Training Slime health: 6
Stage 1 cleared!
Training Slime health: 23
Training Slime health: 16
Training Slime health: 9
Training Slime health: 2
Stage 2 cleared!
Training Slime health: 33
Training Slime health: 26
Training Slime health: 19
Training Slime health: 12
Training Slime health: 5
Stage 3 cleared!
</code></pre>
<p>Three stages.</p>
<p>Each slime tougher than the last.</p>
<p>And the game, not <code>main()</code>, decided all of that.</p>
<hr />
<h2>Test what a player experiences</h2>
<p>Our first tests check individual rules.</p>
<p><code>Game</code> lets us start testing something larger: what happens when those rules work together.</p>
<p>A new game should start at stage 1:</p>
<pre><code class="language-rust">#[test]
fn a_new_game_starts_at_stage_one() {
    let game = Game::new();

    assert_eq!(game.stage, 1);
    assert_eq!(game.enemy.health, 20);
}
</code></pre>
<p>A click should hurt the enemy:</p>
<pre><code class="language-rust">#[test]
fn clicking_damages_the_enemy() {
    let mut game = Game::new();

    game.click();

    assert_eq!(game.enemy.health, 13);
}
</code></pre>
<p>And defeating an enemy should move the game forward:</p>
<pre><code class="language-rust">#[test]
fn defeating_an_enemy_advances_the_stage() {
    let mut game = Game::new();
    game.enemy.health = 1;

    game.click();

    assert_eq!(game.stage, 2);
    assert_eq!(game.enemy.health, 30);
}
</code></pre>
<p>That last test does not care how damage is calculated.</p>
<p>Those smaller rules already have their own tests.</p>
<p>It asks a larger question:</p>
<blockquote>
<p>When a click defeats an enemy, does the game move on?</p>
</blockquote>
<p>Run:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>All eight should pass.</p>
<hr />
<h2>The complete program</h2>
<p>If you are only reading along, you can skip to the last two sections.</p>
<p>If you are coding along, here is everything in one place:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
}

struct Enemy {
    name: String,
    health: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

impl Enemy {
    fn for_stage(stage: u32) -&gt; Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -&gt; Game {
        Game {
            stage: 1,
            player: Player {
                base_damage: 5,
                bonus_damage: 2,
            },
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&amp;mut self) {
        attack(&amp;self.player, &amp;mut self.enemy);

        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&amp;mut self) {
        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -&gt; u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn attack(
    player: &amp;Player,
    enemy: &amp;mut Enemy,
) {
    let damage = click_damage(
        player.base_damage,
        player.bonus_damage,
    );

    enemy.health =
        health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage &lt;= 3 {
        let stage = game.stage;

        game.click();

        if game.stage &gt; stage {
            println!("Stage {stage} cleared!");
        } else {
            println!(
                "{} health: {}",
                game.enemy.name,
                game.enemy.health
            );
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(
            health_after_hit(3, 10),
            0
        );
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player {
            base_damage: 5,
            bonus_damage: 2,
        };

        let mut enemy = Enemy {
            name: String::from("Training Slime"),
            health: 10,
        };

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 3);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 13);
    }

    #[test]
    fn defeating_an_enemy_advances_the_stage() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
    }
}
</code></pre>
<p>Run both:</p>
<pre><code class="language-bash">cargo run
cargo test
</code></pre>
<p>The game should clear three stages, and all eight tests should pass.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li>structs that own other structs</li>
<li><code>impl</code> blocks</li>
<li>associated functions like <code>Enemy::for_stage()</code></li>
<li><code>new()</code> as a convention, not a keyword</li>
<li>methods</li>
<li><code>self</code> and <code>&amp;mut self</code></li>
<li>borrowing different fields at the same time</li>
<li><code>saturating_mul()</code></li>
<li>tests that describe larger game behavior</li>
</ul>
<p>You do not need to memorize all of that yet.</p>
<p>The important change is where the game lives.</p>
<p>We started with a <code>main()</code> that owned everything and decided everything.</p>
<p>We ended with:</p>
<pre><code class="language-rust">let mut game = Game::new();

game.click();
</code></pre>
<p>One click is now one full turn:</p>
<pre><code class="language-text">game.click()
    ↓
attack(&amp;self.player, &amp;mut self.enemy)
    ↓
click_damage()
    ↓
health_after_hit()
    ↓
is_defeated()?
    ↓
defeat_enemy()
    ↓
next stage + Enemy::for_stage()
</code></pre>
<p>Look at the bottom of that chain.</p>
<p><code>click_damage()</code>, <code>health_after_hit()</code>, and <code>is_defeated()</code> are the same small functions we started the series with.</p>
<p>They have not changed.</p>
<p>They are simply a few layers deeper now, inside something that finally looks like a game.</p>
<hr />
<h2>Winning does not pay yet</h2>
<p>Our game has a real loop now.</p>
<p>Click. The slime falls. The next one walks in tougher than the last.</p>
<p>But look at what the player gets for all that clicking:</p>
<pre><code class="language-text">gold:       nothing
experience: nothing
levels:     nothing
</code></pre>
<p>We just cleared three stages.</p>
<p>Our player is exactly as strong as when the game started.</p>
<p>Every slime gets tougher.</p>
<p>Our player never does.</p>
<p>Keep going long enough, and every new slime takes more clicks than the last one.</p>
<p>Forever.</p>
<p>That is not progression.</p>
<p>That is a treadmill.</p>
<p>An idle game needs a reason to keep clicking: rewards for winning, levels to reach, and something to spend those rewards on.</p>
<p>And this time, we already know where those rewards will be handed out:</p>
<pre><code class="language-rust">fn defeat_enemy(&amp;mut self)
</code></pre>
<p>Next, we will make winning worth something.</p>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Borrowing Without the Lecture.]]></title><description><![CDATA[Learn Rust ownership and borrowing by fixing three real compiler errors. Let a player attack an enemy with &Player and &mut Enemy, without a lecture.]]></description><link>https://journeytocode.hashnode.dev/borrowing-without-the-lecture</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/borrowing-without-the-lecture</guid><category><![CDATA[Rust]]></category><category><![CDATA[#beginners #learningtocode #100daysofcode]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[memory-management]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Fri, 16 Oct 2026 01:15:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/474c0b92-0838-4fd7-8012-e2e45f0fba03.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our game has a <code>Player</code> and an <code>Enemy</code></p>
<p>Then, the moment they fight, we strip them back down for parts:</p>
<pre><code class="language-rust">enemy.health = attack(
    enemy.health,
    player.base_damage,
    player.bonus_damage,
);
</code></pre>
<p>We built two real game types.</p>
<p>The moment they interact, we go back to passing loose numbers around.</p>
<p>In this article, we are going to change <code>attack()</code> so it takes the player and the enemy themselves.</p>
<p>Along the way, Rust will refuse to compile our code three times.</p>
<p>Every time, it will be right.</p>
<p>By the end, our combat loop will say what the game actually does:</p>
<pre><code class="language-rust">attack(&amp;player, &amp;mut enemy);
</code></pre>
<p>A player attacks an enemy.</p>
<p>Nothing gets torn apart.</p>
<hr />
<h2>Our starting point</h2>
<p>You need two structs and four small functions to follow along:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
}

struct Enemy {
    name: String,
    health: u32,
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn attack(
    health: u32,
    base_damage: u32,
    bonus_damage: u32,
) -&gt; u32 {
    let damage =
        click_damage(base_damage, bonus_damage);

    health_after_hit(health, damage)
}
</code></pre>
<p>At the bottom of <code>main.rs</code>, the <code>tests</code> module holds three tests for the small rules and one test that calls <code>attack()</code> directly:</p>
<pre><code class="language-rust">#[test]
fn attack_reduces_enemy_health() {
    assert_eq!(attack(10, 5, 2), 3);
}
</code></pre>
<p>We will come back to that test once <code>attack()</code> changes.</p>
<p>Our combat loop looks like this:</p>
<pre><code class="language-rust">fn main() {
    let player = Player {
        base_damage: 5,
        bonus_damage: 2,
    };

    let mut enemy = Enemy {
        name: String::from("Training Slime"),
        health: 20,
    };

    while !is_defeated(enemy.health) {
        enemy.health = attack(
            enemy.health,
            player.base_damage,
            player.bonus_damage,
        );

        println!(
            "{} health: {}",
            enemy.name,
            enemy.health
        );
    }

    println!("{} defeated!", enemy.name);
}
</code></pre>
<p>It works.</p>
<p>But <code>attack()</code> does not know what a player is.</p>
<p>It does not know what an enemy is.</p>
<p>It takes three numbers and hands back a fourth, and <code>main()</code> has to put that number back where it belongs.</p>
<p>What we want to write is closer to:</p>
<pre><code class="language-rust">attack(player, enemy);
</code></pre>
<p>So let us try that.</p>
<hr />
<h2>Hand the player to <code>attack()</code></h2>
<p>Let us start with the player and leave the enemy alone for a moment.</p>
<p>Change <code>attack()</code> so it takes a whole <code>Player</code> instead of two loose damage values:</p>
<pre><code class="language-rust">fn attack(
    player: Player,
    health: u32,
) -&gt; u32 {
    let damage = click_damage(
        player.base_damage,
        player.bonus_damage,
    );

    health_after_hit(health, damage)
}
</code></pre>
<p>The body barely changed.</p>
<p>We read the damage values from the player with the same dot syntax we used in <code>main()</code>.</p>
<p>Now update the call inside the loop:</p>
<pre><code class="language-rust">enemy.health =
    attack(player, enemy.health);
</code></pre>
<p>That reads much better.</p>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>Rust refuses to build the program.</p>
<p>Trimmed down, the error says:</p>
<pre><code class="language-text">error[E0382]: use of moved value: `player`

value moved here, in previous
iteration of loop
</code></pre>
<p>The full message is longer, and it suggests a few fixes, like cloning the player.</p>
<p>Some of them would work.</p>
<p>None of them is what we want, so let us figure out what actually happened.</p>
<p>The first attack would have been fine.</p>
<p>The second attack is the problem.</p>
<p>Where did the player go?</p>
<hr />
<h2>The player moved</h2>
<p>In Rust, every value has an owner.</p>
<p>Right now, <code>main()</code> owns <code>player</code>.</p>
<p>It created it, so it is responsible for it.</p>
<p>When we write:</p>
<pre><code class="language-rust">attack(player, enemy.health)
</code></pre>
<p>and <code>attack()</code> asks for a <code>Player</code>, we are not lending the player to <code>attack()</code>.</p>
<p>We are giving it away.</p>
<p>Ownership moves into <code>attack()</code>.</p>
<p>When <code>attack()</code> finishes, the <code>Player</code> it owns is no longer available back in <code>main()</code>.</p>
<p>So on the next trip around the loop, <code>main()</code> tries to hand over a player it no longer owns.</p>
<p>Rust catches that before the program ever runs.</p>
<p>In game terms, our player attacked once and then vanished.</p>
<p>That is a bold balancing decision.</p>
<p>It is not ours.</p>
<p>The attack was supposed to defeat the enemy.</p>
<p>Not the player.</p>
<hr />
<h2>Why did health never move?</h2>
<p>We have been passing <code>enemy.health</code> into functions all along, and it never disappeared.</p>
<p>That is because <code>u32</code> implements Rust's <code>Copy</code> trait.</p>
<p>When we pass a <code>u32</code> by value, Rust can automatically make a copy, so the original remains available.</p>
<p>Our <code>Player</code> does not implement <code>Copy</code>, so passing it by value moves ownership instead.</p>
<p>You do not need to understand traits yet.</p>
<p>For now, the useful difference is:</p>
<pre><code class="language-text">u32     → copied
Player  → moved
</code></pre>
<p>A <code>u32</code> gets photocopied.</p>
<p>A <code>Player</code> gets handed over.</p>
<p>We do not want to give the player away.</p>
<p>We want <code>attack()</code> to look at it.</p>
<hr />
<h2>Borrow the player with <code>&amp;</code></h2>
<p>Rust lets a function borrow a value instead of owning it.</p>
<p>Add one character to the parameter type:</p>
<pre><code class="language-rust">fn attack(
    player: &amp;Player,
    health: u32,
) -&gt; u32 {
    let damage = click_damage(
        player.base_damage,
        player.bonus_damage,
    );

    health_after_hit(health, damage)
}
</code></pre>
<p><code>&amp;Player</code> means:</p>
<blockquote>
<p>a reference to a <code>Player</code> that someone else owns</p>
</blockquote>
<p>A reference lets <code>attack()</code> reach the player without taking ownership of it.</p>
<p>When the call finishes, the player is still sitting in <code>main()</code> where we left it.</p>
<p>Notice that the body did not change.</p>
<p>The dot works the same way through a reference, so:</p>
<pre><code class="language-rust">player.base_damage
</code></pre>
<p>still reads the field.</p>
<p>The call needs the same <code>&amp;</code>:</p>
<pre><code class="language-rust">enemy.health =
    attack(&amp;player, enemy.health);
</code></pre>
<p><code>&amp;player</code> says:</p>
<blockquote>
<p>let <code>attack()</code> borrow the player for this call</p>
</blockquote>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>The Training Slime goes down in three hits again.</p>
<p>The player survives every one of them.</p>
<p>There is a bonus hiding in that <code>&amp;</code>.</p>
<p>A plain <code>&amp;</code> reference gives <code>attack()</code> read-only access to the player's fields.</p>
<p>If <code>attack()</code> tried to assign a new value to:</p>
<pre><code class="language-rust">player.base_damage
</code></pre>
<p>Rust would refuse to compile it.</p>
<p>That is exactly the promise we want.</p>
<p>An attack needs to read the player.</p>
<p>It does not need to change the player.</p>
<hr />
<h2>Now the enemy</h2>
<p>The player is solved.</p>
<p>The enemy is still split apart:</p>
<pre><code class="language-rust">enemy.health =
    attack(&amp;player, enemy.health);
</code></pre>
<p><code>attack()</code> gets a copy of the enemy's health, calculates a new number, and returns it.</p>
<p>Then <code>main()</code> writes that number back into the enemy.</p>
<p>What we really want is for <code>attack()</code> to change the enemy itself.</p>
<p>We just learned that borrowing keeps a value where it is.</p>
<p>Let us borrow the enemy too:</p>
<pre><code class="language-rust">fn attack(
    player: &amp;Player,
    enemy: &amp;Enemy,
) {
    let damage = click_damage(
        player.base_damage,
        player.bonus_damage,
    );

    enemy.health =
        health_after_hit(enemy.health, damage);
}
</code></pre>
<p>Two things changed besides the enemy parameter.</p>
<p>There is no:</p>
<pre><code class="language-rust">-&gt; u32
</code></pre>
<p>anymore.</p>
<p><code>attack()</code> is not handing back a number for someone else to store.</p>
<p>The result lives in the enemy.</p>
<p>Inside the function, we assign the new health directly to:</p>
<pre><code class="language-rust">enemy.health
</code></pre>
<p>Update the call in <code>main()</code>:</p>
<pre><code class="language-rust">attack(&amp;player, &amp;enemy);
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>Rust refuses again:</p>
<pre><code class="language-text">error[E0594]: cannot assign to
`enemy.health`, which is behind
a `&amp;` reference
</code></pre>
<p>You may also see a warning that <code>enemy</code> does not need to be mutable.</p>
<p>Ignore it for a moment.</p>
<p>We are about to need that <code>mut</code> again.</p>
<p>This is the promise from the last section working against us.</p>
<p><code>&amp;Enemy</code> lets us look at the enemy.</p>
<p>We did not come here to look.</p>
<p>A plain <code>&amp;</code> reference is read-only.</p>
<p>That was perfect for the player.</p>
<p>It is wrong for the enemy because the entire point of an attack is that the enemy's health changes.</p>
<hr />
<h2>Borrow the enemy with <code>&amp;mut</code></h2>
<p>We have already met <code>mut</code>.</p>
<p>It is how we told Rust that our enemy is allowed to change.</p>
<p>References can express the same idea.</p>
<p>Change:</p>
<pre><code class="language-rust">&amp;Enemy
</code></pre>
<p>to:</p>
<pre><code class="language-rust">&amp;mut Enemy
</code></pre>
<p>Our attack becomes:</p>
<pre><code class="language-rust">fn attack(
    player: &amp;Player,
    enemy: &amp;mut Enemy,
) {
    let damage = click_damage(
        player.base_damage,
        player.bonus_damage,
    );

    enemy.health =
        health_after_hit(enemy.health, damage);
}
</code></pre>
<p><code>&amp;mut Enemy</code> means:</p>
<blockquote>
<p>a mutable reference to an <code>Enemy</code> that someone else owns</p>
</blockquote>
<p>In game terms:</p>
<blockquote>
<p><code>attack()</code> may temporarily change this enemy, but it does not own the enemy.</p>
</blockquote>
<p>The call needs to ask for the same thing:</p>
<pre><code class="language-rust">attack(&amp;player, &amp;mut enemy);
</code></pre>
<p>This only works because <code>enemy</code> was already declared with:</p>
<pre><code class="language-rust">let mut enemy
</code></pre>
<p>Rust will not let us borrow something mutably if the owner never agreed that it could change.</p>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">Training Slime health: 13
Training Slime health: 6
Training Slime health: 0
Training Slime defeated!
</code></pre>
<p>The same fight as before.</p>
<p>But now the signature tells the whole story:</p>
<pre><code class="language-text">&amp;Player     → attack reads the player
&amp;mut Enemy  → attack changes the enemy
</code></pre>
<p>Nobody gets consumed.</p>
<p>Nobody gets torn apart.</p>
<p>Both are still in <code>main()</code>, ready for the next attack.</p>
<hr />
<h2>One rule worth knowing now</h2>
<p>There is one borrowing rule you will run into early, so here it is in game terms.</p>
<p>Suppose that, instead of the combat loop, <code>main()</code> grabs a reference to the enemy's name before attacking:</p>
<pre><code class="language-rust">let name = &amp;enemy.name;

attack(&amp;player, &amp;mut enemy);

println!("{name} took a hit!");
</code></pre>
<p>Rust refuses for the third time:</p>
<pre><code class="language-text">error[E0502]: cannot borrow `enemy`
as mutable because it is also
borrowed as immutable
</code></pre>
<p>A borrow remains active until its last use.</p>
<p><code>name</code> is used by the <code>println!()</code> after the attack, so that borrow is still active when <code>attack()</code> asks for permission to change the enemy.</p>
<p>Rust will not hand out a mutable borrow while that reading borrow is still being used.</p>
<p>Our combat loop never hits this problem because each borrow is finished before the next conflicting access begins:</p>
<pre><code class="language-rust">attack(&amp;player, &amp;mut enemy);

println!(
    "{} health: {}",
    enemy.name,
    enemy.health
);
</code></pre>
<p>In this case, the mutable borrow is finished when <code>attack()</code> returns.</p>
<p>Then <code>println!()</code> is free to read the enemy.</p>
<p>That is the beginner version of the rule:</p>
<blockquote>
<p><strong>For the same data at the same time: many readers, or one writer, but not both.</strong></p>
</blockquote>
<p>You do not need more than that today.</p>
<hr />
<h2>Update the test</h2>
<p>Our <code>attack()</code> test still passes three loose numbers, so it needs the new signature:</p>
<pre><code class="language-rust">#[test]
fn attack_reduces_enemy_health() {
    let player = Player {
        base_damage: 5,
        bonus_damage: 2,
    };

    let mut enemy = Enemy {
        name: String::from("Training Slime"),
        health: 10,
    };

    attack(&amp;player, &amp;mut enemy);

    assert_eq!(enemy.health, 3);
}
</code></pre>
<p>The rule it checks has not changed.</p>
<p>An attack with five base damage and two bonus damage takes ten health down to three.</p>
<p>What changed is how the test says it.</p>
<p>It builds a player.</p>
<p>It builds an enemy.</p>
<p>It lets one attack the other.</p>
<p>Then it checks the enemy afterward.</p>
<p>That reads like the game.</p>
<p>The other three tests do not touch <code>attack()</code>, so they stay exactly as they were.</p>
<p>Run them:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>All four should pass.</p>
<p>We do not need a separate test proving that <code>attack()</code> does not change the player.</p>
<p>With our current <code>Player</code> type, an <code>&amp;Player</code> reference does not allow <code>attack()</code> to assign new values to the player's fields.</p>
<p>The compiler checks that for us every time we build.</p>
<hr />
<h2>The complete program</h2>
<p>If you have been following along in your editor, here is everything in one place.</p>
<p>At this point, <code>src/main.rs</code> should look like this:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
}

struct Enemy {
    name: String,
    health: u32,
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn attack(
    player: &amp;Player,
    enemy: &amp;mut Enemy,
) {
    let damage = click_damage(
        player.base_damage,
        player.bonus_damage,
    );

    enemy.health =
        health_after_hit(enemy.health, damage);
}

fn main() {
    let player = Player {
        base_damage: 5,
        bonus_damage: 2,
    };

    let mut enemy = Enemy {
        name: String::from("Training Slime"),
        health: 20,
    };

    while !is_defeated(enemy.health) {
        attack(&amp;player, &amp;mut enemy);

        println!(
            "{} health: {}",
            enemy.name,
            enemy.health
        );
    }

    println!("{} defeated!", enemy.name);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(
            health_after_hit(3, 10),
            0
        );
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player {
            base_damage: 5,
            bonus_damage: 2,
        };

        let mut enemy = Enemy {
            name: String::from("Training Slime"),
            health: 10,
        };

        attack(&amp;player, &amp;mut enemy);

        assert_eq!(enemy.health, 3);
    }
}
</code></pre>
<p>Run both:</p>
<pre><code class="language-bash">cargo run
cargo test
</code></pre>
<p>The Training Slime should fall in three hits, and all four tests should pass.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li><p>ownership</p>
</li>
<li><p>moves</p>
</li>
<li><p><code>Copy</code> values</p>
</li>
<li><p><code>&amp;T</code> references</p>
</li>
<li><p><code>&amp;mut T</code> references</p>
</li>
<li><p>functions that change a value instead of returning a new one</p>
</li>
<li><p>when borrows stop being used</p>
</li>
<li><p>why Rust prevents conflicting access</p>
</li>
</ul>
<p>You do not need to memorize all of that yet.</p>
<p>The important change is what our attack says.</p>
<p>We started with:</p>
<pre><code class="language-rust">enemy.health = attack(
    enemy.health,
    player.base_damage,
    player.bonus_damage,
);
</code></pre>
<p>We ended with:</p>
<pre><code class="language-rust">attack(&amp;player, &amp;mut enemy);
</code></pre>
<p>The player is read.</p>
<p>The enemy is changed.</p>
<p>Both stay where they belong.</p>
<p>And the compiler now checks that <code>attack()</code> keeps its side of that deal.</p>
<hr />
<h2>There is a bigger shape missing</h2>
<p>Look at what <code>main()</code> is holding now:</p>
<pre><code class="language-text">player
enemy
the combat loop
</code></pre>
<p>That is a fight.</p>
<p>It is not a game.</p>
<p>When the Training Slime dies, the program simply ends.</p>
<p>There is no next enemy.</p>
<p>There is no stage counter.</p>
<p>Nobody earns anything for winning.</p>
<p>And every one of those things would become another loose variable inside <code>main()</code>:</p>
<pre><code class="language-text">player
enemy
stage
gold
experience
</code></pre>
<p>We have seen this problem before.</p>
<p>Three numbers were not a player.</p>
<p>A pile of variables in <code>main()</code> is not a game.</p>
<p>The pieces that make up our game need one place to live, and one place that decides what happens when a player clicks.</p>
<p>Next, we will put the game in one place.</p>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Three Numbers Are Not a Player.]]></title><description><![CDATA[When your attack takes three loose u32 values, Rust has no idea which belong to the player. Learn how structs give your game data a name and a shape.]]></description><link>https://journeytocode.hashnode.dev/three-numbers-are-not-a-player</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/three-numbers-are-not-a-player</guid><category><![CDATA[Rust programming]]></category><category><![CDATA[rust lang]]></category><category><![CDATA[beginner]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[WebAssembly]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Wed, 14 Oct 2026 02:06:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/6b2fb2c6-8e19-479e-9b05-d9e3fad91568.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Look at this line:</p>
<pre><code class="language-rust">attack(enemy_health, 5, 2)
</code></pre>
<p>Three values. Two belong to the player. One belongs to the enemy.</p>
<p>Nothing in the code says which is which.</p>
<p>In this article, we are going to fix that by grouping those values into two new Rust types: a <code>Player</code> and an <code>Enemy</code>.</p>
<p>By the end, our code will start describing the things that actually exist in our game instead of asking us to remember what those numbers represent.</p>
<hr />
<h2>Our starting point</h2>
<p>You only need four small functions to follow along:</p>
<pre><code class="language-rust">fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn attack(
    health: u32,
    base_damage: u32,
    bonus_damage: u32,
) -&gt; u32 {
    let damage =
        click_damage(base_damage, bonus_damage);

    health_after_hit(health, damage)
}
</code></pre>
<p>Our combat loop currently looks like this:</p>
<pre><code class="language-rust">fn main() {
    let mut enemy_health = 20;

    while !is_defeated(enemy_health) {
        enemy_health =
            attack(enemy_health, 5, 2);

        println!(
            "Enemy health: {enemy_health}"
        );
    }

    println!("Enemy defeated!");
}
</code></pre>
<p>This code works.</p>
<p>The problem is not what it does.</p>
<p>The problem is what it says.</p>
<hr />
<h2>Three numbers with three different meanings</h2>
<p>Look at the attack again:</p>
<pre><code class="language-rust">attack(enemy_health, 5, 2)
</code></pre>
<p>The first value represents the enemy.</p>
<p>The other two belong to the player.</p>
<p>But nothing in the function call makes those relationships clear.</p>
<p>Someone reading this code has to remember:</p>
<pre><code class="language-text">enemy_health → the enemy's current health
5            → the player's base damage
2            → the player's bonus damage
</code></pre>
<p>That is manageable today.</p>
<p>It will become harder when our player also has:</p>
<ul>
<li><p>a level</p>
</li>
<li><p>experience</p>
</li>
<li><p>gold</p>
</li>
<li><p>upgrade levels</p>
</li>
<li><p>passive damage</p>
</li>
</ul>
<p>The enemy will need more information too:</p>
<ul>
<li><p>a name</p>
</li>
<li><p>maximum health</p>
</li>
<li><p>gold rewards</p>
</li>
<li><p>experience rewards</p>
</li>
</ul>
<p>Passing every single value as an individual number just isn't going to scale.</p>
<p>We need a way to tell Rust which values belong together.</p>
<hr />
<h2>Give the player some shape</h2>
<p>Rust lets us create our own types with <code>struct</code>.</p>
<p>Add this near the top of <code>main.rs</code>:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
}
</code></pre>
<p>A <strong>struct</strong> is a type that groups related values together.</p>
<p>Our new type is called:</p>
<pre><code class="language-text">Player
</code></pre>
<p>It contains two named values:</p>
<pre><code class="language-text">base_damage
bonus_damage
</code></pre>
<p>The named values inside a struct are called <strong>fields</strong>.</p>
<p>Each field has a type. The colon syntax is the same as a function parameter—it connects the name to the type:</p>
<pre><code class="language-rust">base_damage: u32
bonus_damage: u32
</code></pre>
<p>So every <code>Player</code> has a field named <code>base_damage</code> that contains a <code>u32</code>, and a field named <code>bonus_damage</code> that also contains a <code>u32</code>.</p>
<p>The struct definition describes the shape of a player.</p>
<p>It does not create one yet.</p>
<hr />
<h2>Create a player</h2>
<p>Now that Rust knows what a <code>Player</code> looks like, we can create one:</p>
<pre><code class="language-rust">let player = Player {
    base_damage: 5,
    bonus_damage: 2,
};
</code></pre>
<p>This creates a value with a type of: <code>Player</code>.</p>
<p>The values line up with the fields from our definition:</p>
<pre><code class="language-text">Player
├── base_damage: 5
└── bonus_damage: 2
</code></pre>
<p>We can access a field with a dot:</p>
<pre><code class="language-rust">player.base_damage
</code></pre>
<p>That gives us the player's base damage.</p>
<p>We can do the same for bonus damage:</p>
<pre><code class="language-rust">player.bonus_damage
</code></pre>
<p>Our attack call can now become:</p>
<pre><code class="language-rust">attack(
    enemy_health,
    player.base_damage,
    player.bonus_damage,
)
</code></pre>
<p>That does happen to be longer than:</p>
<pre><code class="language-rust">attack(enemy_health, 5, 2)
</code></pre>
<p>But it is also clearer.</p>
<p>We no longer have to remember what <code>5</code> and <code>2</code> represent.</p>
<p>The code tells us.</p>
<hr />
<h2>Give the enemy some shape</h2>
<p>Our enemy is still represented by one loose variable:</p>
<pre><code class="language-rust">let mut enemy_health = 20;
</code></pre>
<p>Health is not the only thing an enemy needs.</p>
<p>Even giving it a name creates another related value.</p>
<p>Add another struct:</p>
<pre><code class="language-rust">struct Enemy {
    name: String,
    health: u32,
}
</code></pre>
<p>Our new <code>Enemy</code> type contains two fields:</p>
<pre><code class="language-text">Enemy
├── name
└── health
</code></pre>
<p>We already know the type of <code>health</code>:</p>
<pre><code class="language-rust">health: u32
</code></pre>
<p>The name introduces a new type:</p>
<pre><code class="language-rust">name: String
</code></pre>
<hr />
<h2>Give the enemy a name</h2>
<p><code>String</code> is a Rust type for storing owned text.</p>
<p>That fits our enemy: it should keep its own name for as long as it exists.</p>
<p>We can create a <code>String</code> from text like this:</p>
<pre><code class="language-rust">String::from("Training Slime")
</code></pre>
<p>For now, read that as:</p>
<blockquote>
<p>Create a <code>String</code> containing "Training Slime."</p>
</blockquote>
<p>Our game only needs enough text to name an enemy.</p>
<p>Now we can create one:</p>
<pre><code class="language-rust">let enemy = Enemy {
    name: String::from("Training Slime"),
    health: 20,
};
</code></pre>
<p>Rust now understands that the name and health belong to the same enemy.</p>
<hr />
<h2>The enemy still needs to change</h2>
<p>There is one problem with our new enemy:</p>
<pre><code class="language-rust">let enemy = Enemy {
    name: String::from("Training Slime"),
    health: 20,
};
</code></pre>
<p>Combat changes health.</p>
<p>But <code>enemy</code> is immutable by default.</p>
<p>If we try to assign a new value to:</p>
<pre><code class="language-rust">enemy.health
</code></pre>
<p>Rust will refuse to compile the program.</p>
<p>We have already solved this problem once.</p>
<p>The enemy needs to be mutable:</p>
<pre><code class="language-rust">let mut enemy = Enemy {
    name: String::from("Training Slime"),
    health: 20,
};
</code></pre>
<p>The <code>mut</code> applies to the <code>enemy</code> value, which allows us to change its fields:</p>
<pre><code class="language-rust">enemy.health = 13;
</code></pre>
<p>We are not adding mutability everywhere.</p>
<p>The player does not need to change yet:</p>
<pre><code class="language-rust">let player = Player {
</code></pre>
<p>The enemy does:</p>
<pre><code class="language-rust">let mut enemy = Enemy {
</code></pre>
<p>Once again, the game tells us which values need to be mutable.</p>
<hr />
<h2>Put the player and enemy into combat</h2>
<p>We can now update <code>main()</code>:</p>
<pre><code class="language-rust">fn main() {
    let player = Player {
        base_damage: 5,
        bonus_damage: 2,
    };

    let mut enemy = Enemy {
        name: String::from("Training Slime"),
        health: 20,
    };

    while !is_defeated(enemy.health) {
        enemy.health = attack(
            enemy.health,
            player.base_damage,
            player.bonus_damage,
        );

        println!(
            "{} health: {}",
            enemy.name,
            enemy.health
        );
    }

    println!("{} defeated!", enemy.name);
}
</code></pre>
<p>There is one small change in our <code>println!()</code> calls.</p>
<p>The <code>{}</code> placeholders are filled from left to right by the values that follow the format string:</p>
<pre><code class="language-rust">println!(
    "{} health: {}",
    enemy.name,
    enemy.health
);
</code></pre>
<p>The first <code>{}</code> displays <code>enemy.name</code>.</p>
<p>The second displays <code>enemy.health</code>.</p>
<p>Run the game:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">Training Slime health: 13
Training Slime health: 6
Training Slime health: 0
Training Slime defeated!
</code></pre>
<p>The combat still works.</p>
<p>But the code now contains an actual player and enemy.</p>
<hr />
<h2>Notice what the structs made visible</h2>
<p>Before, our game looked like this:</p>
<pre><code class="language-rust">let mut enemy_health = 20;

enemy_health =
    attack(enemy_health, 5, 2);
</code></pre>
<p>Now it looks like this:</p>
<pre><code class="language-rust">let player = Player {
    base_damage: 5,
    bonus_damage: 2,
};

let mut enemy = Enemy {
    name: String::from("Training Slime"),
    health: 20,
};

enemy.health = attack(
    enemy.health,
    player.base_damage,
    player.bonus_damage,
);
</code></pre>
<p>The behavior is the same.</p>
<p>The meaning is not.</p>
<p>Before:</p>
<pre><code class="language-text">20
5
2
</code></pre>
<p>Now:</p>
<pre><code class="language-text">Player
├── base_damage
└── bonus_damage

Enemy
├── name
└── health
</code></pre>
<p>We gave related values names and grouped them into types.</p>
<p>Rust can now help us keep player data with the player and enemy data with the enemy.</p>
<hr />
<h2>The behavior did not change</h2>
<p>Notice what we did not touch:</p>
<pre><code class="language-rust">click_damage()
</code></pre>
<p>still calculates damage.</p>
<pre><code class="language-rust">health_after_hit()
</code></pre>
<p>still reduces health.</p>
<pre><code class="language-rust">is_defeated()
</code></pre>
<p>still checks for defeat.</p>
<pre><code class="language-rust">attack()
</code></pre>
<p>still combines the smaller rules.</p>
<p>We changed how the application organizes its data.</p>
<p>We did not rewrite the behavior we already trust.</p>
<p>Run the tests:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>All four should still pass.</p>
<p>No new combat rule was added in this article, so we do not need to invent a new behavior test just to prove that a struct can hold the values we typed into it.</p>
<p>The compiler checks that our struct fields exist and contain the expected types.</p>
<p>Our existing tests continue checking the game rules.</p>
<hr />
<h2>The complete program</h2>
<p>At this point, <code>src/main.rs</code> should look like this:</p>
<pre><code class="language-rust">struct Player {
    base_damage: u32,
    bonus_damage: u32,
}

struct Enemy {
    name: String,
    health: u32,
}

fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn attack(
    health: u32,
    base_damage: u32,
    bonus_damage: u32,
) -&gt; u32 {
    let damage =
        click_damage(base_damage, bonus_damage);

    health_after_hit(health, damage)
}

fn main() {
    let player = Player {
        base_damage: 5,
        bonus_damage: 2,
    };

    let mut enemy = Enemy {
        name: String::from("Training Slime"),
        health: 20,
    };

    while !is_defeated(enemy.health) {
        enemy.health = attack(
            enemy.health,
            player.base_damage,
            player.bonus_damage,
        );

        println!(
            "{} health: {}",
            enemy.name,
            enemy.health
        );
    }

    println!("{} defeated!", enemy.name);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(
            health_after_hit(3, 10),
            0
        );
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        assert_eq!(attack(10, 5, 2), 3);
    }
}
</code></pre>
<p>Run both:</p>
<pre><code class="language-bash">cargo run
cargo test
</code></pre>
<p>The game should defeat the Training Slime, and all four tests should pass.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li><p><code>struct</code></p>
</li>
<li><p>custom Rust types</p>
</li>
<li><p>struct fields</p>
</li>
<li><p>creating struct values</p>
</li>
<li><p>field access with <code>.</code></p>
</li>
<li><p><code>String</code></p>
</li>
<li><p>mutable struct values</p>
</li>
</ul>
<p>You do not need to memorize all of that yet.</p>
<p>The important change is what happened to our data.</p>
<p>We started with:</p>
<pre><code class="language-text">enemy_health
5
2
</code></pre>
<p>We turned those values into:</p>
<pre><code class="language-text">Player
Enemy
</code></pre>
<p>Our program now has a vocabulary that matches the game we are building.</p>
<hr />
<h2>There is still something awkward</h2>
<p>We solved one problem.</p>
<p>But look at our attack again:</p>
<pre><code class="language-rust">enemy.health = attack(
    enemy.health,
    player.base_damage,
    player.bonus_damage,
);
</code></pre>
<p>We created a <code>Player</code>.</p>
<p>We created an <code>Enemy</code>.</p>
<p>Then, the moment they need to interact, we pull them apart into individual fields.</p>
<p><code>attack()</code> does not accept a player.</p>
<p>It does not accept an enemy.</p>
<p>It still accepts three numbers and returns another number that we manually assign back to the enemy.</p>
<p>What we really want to express is:</p>
<pre><code class="language-text">player attacks enemy
</code></pre>
<p>That means our attack needs to:</p>
<pre><code class="language-text">read the player
change the enemy
leave both available for the next attack
</code></pre>
<p>Rust has a way to let a function temporarily use values without taking them away from the code that already owns them.</p>
<p>That is called <strong>borrowing</strong>.</p>
<p>Next, we will let our player attack an enemy without consuming either one.</p>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[One Attack Is Not a Fight.]]></title><description><![CDATA[Learn Rust by turning small functions into a combat loop. Build an attack() function, use mut and while, and create your first real game mechanic.]]></description><link>https://journeytocode.hashnode.dev/build-a-combat-loop-in-rust</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/build-a-combat-loop-in-rust</guid><category><![CDATA[Rust]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[Rust programming]]></category><category><![CDATA[Beginner Projects]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 11 Oct 2026 23:14:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/55fef71f-482c-416f-a3ae-0006887988b5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our game can now calculate damage, reduce health, and decide whether an enemy has been defeated.</p>
<p>There is a problem, though.</p>
<p>Those rules do not actually <strong>attack</strong> anything yet.</p>
<p>In this article, we are going to combine our small Rust functions into a larger <code>attack()</code> function, let enemy health change during combat, and repeat attacks until the enemy is defeated.</p>
<p>By the end, we will have our first actual game mechanic: a combat loop.</p>
<hr />
<h2>Our starting point</h2>
<p>You only need three small functions to follow along:</p>
<pre><code class="language-rust">fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}
</code></pre>
<p>Each one has a single job.</p>
<p><code>click_damage()</code> calculates how much damage the player deals.</p>
<p><code>health_after_hit()</code> applies that damage to an enemy's health.</p>
<p><code>is_defeated()</code> tells us whether the enemy has reached zero health.</p>
<p>Right now, however, something else has to coordinate all three.</p>
<p>That something is <code>main()</code>.</p>
<hr />
<h2><code>main()</code> knows too much</h2>
<p>We could attack an enemy like this:</p>
<pre><code class="language-rust">fn main() {
    let enemy_health = 10;

    let damage = click_damage(5, 2);

    let remaining_health =
        health_after_hit(enemy_health, damage);

    println!("Enemy health: {remaining_health}");

    if is_defeated(remaining_health) {
        println!("Enemy defeated!");
    }
}
</code></pre>
<p>Technically, this does work.</p>
<p>But look at everything that <code>main()</code> knows about.</p>
<p>It knows how damage is calculated.</p>
<p>It knows how damage affects health.</p>
<p>It knows how to determine whether the enemy has been defeated.</p>
<p>Those first two steps describe one larger idea:</p>
<p><strong>an attack.</strong></p>
<p>So let us give that idea a function.</p>
<hr />
<h2>Build an attack from smaller functions</h2>
<p>Add this beneath the functions we already have:</p>
<pre><code class="language-rust">fn attack(
    health: u32,
    base_damage: u32,
    bonus_damage: u32,
) -&gt; u32 {
    let damage =
        click_damage(base_damage, bonus_damage);

    health_after_hit(health, damage)
}
</code></pre>
<p>Notice how we are not adding new Rust ideas here.</p>
<p>That is the point.</p>
<p>We are taking functions we already understand and using them to build something more complex.</p>
<p>The first line inside <code>attack()</code>:</p>
<pre><code class="language-rust">let damage =
    click_damage(base_damage, bonus_damage);
</code></pre>
<p>uses our existing damage rule.</p>
<p>Then:</p>
<pre><code class="language-rust">health_after_hit(health, damage)
</code></pre>
<p>uses the result to calculate the enemy's remaining health.</p>
<p>Because it is the final expression and has no semicolon, that remaining health becomes the value returned by <code>attack()</code>.</p>
<p>Our functions now fit together like this:</p>
<pre><code class="language-text">click_damage() ───────┐
                      ├──→ attack()
health_after_hit() ───┘
</code></pre>
<p>We did not replace our small functions.</p>
<p><strong>We composed a larger behavior from them.</strong></p>
<p>Instead of teaching <code>attack()</code> every rule from scratch, it delegates those jobs to functions that already know how to calculate damage and reduce health.</p>
<p>We already know what each piece does, so understanding <code>attack()</code> does not require us to understand an entirely new system.</p>
<hr />
<h2>Try the attack</h2>
<p>Replace <code>main()</code> with:</p>
<pre><code class="language-rust">fn main() {
    let enemy_health = 10;

    let remaining_health =
        attack(enemy_health, 5, 2);

    println!("Enemy health: {remaining_health}");
}
</code></pre>
<p>Run the game:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">Enemy health: 3
</code></pre>
<p>Our player dealt seven damage to an enemy with ten health.</p>
<p>That means <code>attack()</code> works.</p>
<p>We can test it too.</p>
<p>Add this to the test module:</p>
<pre><code class="language-rust">#[test]
fn attack_reduces_enemy_health() {
    assert_eq!(attack(10, 5, 2), 3);
}
</code></pre>
<p>Then run:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>We now have a test for behavior that depends on several smaller rules working together.</p>
<hr />
<h2>One attack is not much of a fight</h2>
<p>We can attack an enemy once.</p>
<p>An idle game probably needs a little more combat than that.</p>
<p>Suppose our enemy starts with twenty health:</p>
<pre><code class="language-rust">let enemy_health = 20;
</code></pre>
<p>The first attack reduces it to thirteen.</p>
<p>The next should reduce it to six.</p>
<p>The next should reduce it to zero.</p>
<p>There is a problem, though.</p>
<p>This:</p>
<pre><code class="language-rust">let enemy_health = 20;
</code></pre>
<p>creates a variable that Rust does not allow us to change.</p>
<p>Variables are <strong>immutable by default</strong> in Rust.</p>
<p>That means this is not allowed:</p>
<pre><code class="language-rust">let enemy_health = 20;

enemy_health = attack(enemy_health, 5, 2);
</code></pre>
<p>If you try this, Rust will refuse to compile it and tell us that we cannot assign to an immutable variable.</p>
<p>That error is useful. We have to explicitly tell Rust which values we expect to change.</p>
<p>For most values, that is a useful default.</p>
<p>For health during combat, it is not.</p>
<p>Health is supposed to change.</p>
<hr />
<h2>Let health change with <code>mut</code></h2>
<p>Rust uses the <code>mut</code> keyword when we want a variable to be mutable.</p>
<p>Change:</p>
<pre><code class="language-rust">let enemy_health = 20;
</code></pre>
<p>to:</p>
<pre><code class="language-rust">let mut enemy_health = 20;
</code></pre>
<p>Now we can assign a new value to it:</p>
<pre><code class="language-rust">enemy_health =
    attack(enemy_health, 5, 2);
</code></pre>
<p>The word <code>mut</code> is short for <strong>mutable</strong>, meaning the value bound to this variable may change.</p>
<p>We are not making everything mutable just because we can.</p>
<p>We are using mutability because enemy health represents something that changes as part of the game.</p>
<p>That distinction will become increasingly useful as our state grows.</p>
<hr />
<h2>Attack again</h2>
<p>We can now do this:</p>
<pre><code class="language-rust">fn main() {
    let mut enemy_health = 20;

    enemy_health =
        attack(enemy_health, 5, 2);

    println!("Enemy health: {enemy_health}");

    enemy_health =
        attack(enemy_health, 5, 2);

    println!("Enemy health: {enemy_health}");

    enemy_health =
        attack(enemy_health, 5, 2);

    println!("Enemy health: {enemy_health}");
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>and we get:</p>
<pre><code class="language-text">Enemy health: 13
Enemy health: 6
Enemy health: 0
</code></pre>
<p>It works.</p>
<p>It is also repetitive.</p>
<p>We know what should happen:</p>
<blockquote>
<p>Keep attacking while the enemy is still alive.</p>
</blockquote>
<p>Rust has a way to express exactly that.</p>
<hr />
<h2>Repeat with <code>while</code></h2>
<p>A <code>while</code> loop repeats a block of code as long as a condition remains <code>true</code>.</p>
<p>For example:</p>
<pre><code class="language-rust">while enemy_health &gt; 0 {
    // repeat something
}
</code></pre>
<p>We could use that.</p>
<p>But we already wrote a function that knows whether the enemy has been defeated:</p>
<pre><code class="language-rust">is_defeated(enemy_health)
</code></pre>
<p>We should use the rule we already have.</p>
<p>What we really want to say is:</p>
<blockquote>
<p>Keep attacking while the enemy is <strong>not</strong> defeated.</p>
</blockquote>
<p>Rust uses <code>!</code> to mean <strong>not</strong>.</p>
<p>So:</p>
<pre><code class="language-rust">!is_defeated(enemy_health)
</code></pre>
<p>means:</p>
<blockquote>
<p>the enemy has not been defeated</p>
</blockquote>
<p>Put that inside a <code>while</code> loop:</p>
<pre><code class="language-rust">while !is_defeated(enemy_health) {
    enemy_health =
        attack(enemy_health, 5, 2);

    println!("Enemy health: {enemy_health}");
}
</code></pre>
<p>Now Rust keeps running the block until <code>is_defeated()</code> returns <code>true</code>.</p>
<hr />
<h2>Our first combat loop</h2>
<p>Update <code>main()</code>:</p>
<pre><code class="language-rust">fn main() {
    let mut enemy_health = 20;

    while !is_defeated(enemy_health) {
        enemy_health =
            attack(enemy_health, 5, 2);

        println!(
            "Enemy health: {enemy_health}"
        );
    }

    println!("Enemy defeated!");
}
</code></pre>
<p>Run:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">Enemy health: 13
Enemy health: 6
Enemy health: 0
Enemy defeated!
</code></pre>
<p>We have a combat loop.</p>
<p>The player attacks.</p>
<p>Health changes.</p>
<p>The game checks whether combat should continue.</p>
<p>Then it attacks again.</p>
<p>Our little system now looks something like this:</p>
<pre><code class="language-text">click_damage() ───────┐
                      ├──→ attack()
health_after_hit() ───┘
                      ↓
                  repeat while
          enemy has not been defeated
</code></pre>
<p>The individual pieces are still small.</p>
<p>The behavior is getting larger.</p>
<hr />
<h2>Notice what Rust made us say</h2>
<p>We encountered three small pieces of Rust syntax in this article:</p>
<pre><code class="language-text">mut    → health needed to change
while  → combat needed to repeat
!      → we needed to express "not defeated"
</code></pre>
<p>But we did not add them because they appeared next on a Rust checklist.</p>
<p>The game gave us reasons to need them.</p>
<p>Enemy health needed to change:</p>
<pre><code class="language-rust">let mut enemy_health = 20;
</code></pre>
<p>Combat needed to repeat until the enemy is defeated:</p>
<pre><code class="language-rust">while !is_defeated(enemy_health) {
</code></pre>
<p>That is how we are going to keep learning Rust throughout this project.</p>
<p>When the game creates a problem, we will learn the Rust needed to solve it.</p>
<hr />
<h2>Testing the larger rule</h2>
<p>Our existing tests still protect the smaller rules:</p>
<pre><code class="language-rust">#[test]
fn click_damage_adds_base_and_bonus() {
    assert_eq!(click_damage(3, 4), 7);
}

#[test]
fn health_never_drops_below_zero() {
    assert_eq!(health_after_hit(3, 10), 0);
}

#[test]
fn zero_health_is_defeated() {
    assert!(is_defeated(0));
    assert!(!is_defeated(1));
}
</code></pre>
<p>Now we also have:</p>
<pre><code class="language-rust">#[test]
fn attack_reduces_enemy_health() {
    assert_eq!(attack(10, 5, 2), 3);
}
</code></pre>
<p>This test is slightly different.</p>
<p><code>attack()</code> is not doing one tiny calculation by itself.</p>
<p>It relies on other functions.</p>
<p>That means we are beginning to test not only individual rules, but also the larger behavior created when those rules work together.</p>
<hr />
<h2>The complete program</h2>
<p>At this point, <code>src/main.rs</code> should look like this:</p>
<pre><code class="language-rust">fn click_damage(
    base: u32,
    bonus: u32,
) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn attack(
    health: u32,
    base_damage: u32,
    bonus_damage: u32,
) -&gt; u32 {
    let damage =
        click_damage(base_damage, bonus_damage);

    health_after_hit(health, damage)
}

fn main() {
    let mut enemy_health = 20;

    while !is_defeated(enemy_health) {
        enemy_health =
            attack(enemy_health, 5, 2);

        println!(
            "Enemy health: {enemy_health}"
        );
    }

    println!("Enemy defeated!");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(
            health_after_hit(3, 10),
            0
        );
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        assert_eq!(attack(10, 5, 2), 3);
    }
}
</code></pre>
<p>Run both:</p>
<pre><code class="language-bash">cargo run
cargo test
</code></pre>
<p>Our game now fights until the enemy is defeated, and all four functions we have written are covered by tests.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we encountered:</p>
<ul>
<li>functions calling other functions</li>
<li>local variables inside functions</li>
<li><code>mut</code></li>
<li>reassignment</li>
<li><code>while</code></li>
<li><code>!</code></li>
<li>tests for composed behavior</li>
</ul>
<p>You do not need to memorize all of that yet.</p>
<p>The important change is what happened to our game.</p>
<p>We started with three independent rules:</p>
<pre><code class="language-text">damage
health
defeat
</code></pre>
<p>We combined them into:</p>
<pre><code class="language-text">attack
</code></pre>
<p>Then repeated that attack until we had:</p>
<pre><code class="language-text">combat
</code></pre>
<p>Our program became more capable without any individual function becoming particularly complicated.</p>
<hr />
<h2>There is another problem hiding in our code</h2>
<p>Look at this:</p>
<pre><code class="language-rust">attack(enemy_health, 5, 2)
</code></pre>
<p>We know what those values mean because we just wrote them.</p>
<p><code>enemy_health</code> belongs to the enemy.</p>
<p><code>5</code> is the player's base damage.</p>
<p><code>2</code> is the player's bonus damage.</p>
<p>Rust does not know any of that.</p>
<p>As far as Rust is concerned, they are just three <code>u32</code> values being passed into a function.</p>
<p>And our game is about to need more information:</p>
<pre><code class="language-text">player level
player gold
enemy name
enemy maximum health
enemy rewards
</code></pre>
<p>Passing more and more unrelated-looking numbers around is not going to age well.</p>
<p>The next problem is no longer about making our functions stronger.</p>
<p>It is about giving our data some shape.</p>
<p>Next, we will turn the player and enemy into real Rust types of their own.</p>
<p><strong>Small function. Bigger game.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Skip Hello World.]]></title><description><![CDATA[Learn Rust by building real game logic instead of stopping at Hello World. Write and test three small functions for damage, health, and enemy defeat.]]></description><link>https://journeytocode.hashnode.dev/skip-hello-world</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/skip-hello-world</guid><category><![CDATA[Rust]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Beginner Projects]]></category><category><![CDATA[Rust programming]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Thu, 08 Oct 2026 22:03:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/e18d1ef9-4700-4a6a-b1d4-b6bf6278652b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Are you curious about diving into Rust, but are tired of tutorials that stop at <code>Hello World!</code>?</p>
<p>In this article, I am going to walk you through writing and testing three tiny Rust functions that do something slightly more interesting: calculate attack damage, reduce an enemy's health, and decide whether the enemy has been defeated.</p>
<p>You do not need any previous Rust experience to follow along.</p>
<p>If you are able to edit a file and run a command in your terminal, you can jump right in.</p>
<p>By the end, we will have the first real rules of an idle game, and these functions will not be throwaway tutorial code. We will keep building with them until they are running inside a complete browser game powered by Rust compiled to WebAssembly.</p>
<p>If you are getting excited, then without further ado, it is time to start dealing damage!</p>
<hr />
<h2>Create the project</h2>
<p>Our first step is ensuring that Rust is installed on our local machine.</p>
<blockquote>
<p><strong>Need Rust installed first?</strong><br />Rust is installed and managed through <code>rustup</code>, the official Rust toolchain installer. Follow the <a href="https://rust-lang.org/install.html">official Rust installation instructions</a> for your operating system, then open a new terminal when the installation finishes.</p>
</blockquote>
<p>You can check that everything is ready with:</p>
<pre><code class="language-bash">rustc --version
cargo --version
</code></pre>
<p>If both commands print version numbers, we are all set.</p>
<p>We can create a new project by typing:</p>
<pre><code class="language-bash">cargo new rustbound-idle
cd rustbound-idle
</code></pre>
<p>Cargo will create a small Rust application for us:</p>
<pre><code class="language-text">rustbound-idle/
├── Cargo.toml
└── src/
    └── main.rs
</code></pre>
<p>Open <code>src/main.rs</code>.</p>
<p>Notice the traditional Rust greeting:</p>
<pre><code class="language-rust">fn main() {
    println!("Hello, world!");
}
</code></pre>
<p>If you want, you can run it once:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>Then chuck it in the bin.</p>
<p>We are not here to say hello to enemies.</p>
<p>We are here to say goodbye to them.</p>
<hr />
<h2>Our first game rule</h2>
<p>Imagine our player has two sources of attack power:</p>
<ul>
<li><p>base damage</p>
</li>
<li><p>bonus damage from upgrades</p>
</li>
</ul>
<p>Calculating total click damage is just addition.</p>
<p>Add this to <code>main.rs</code>:</p>
<pre><code class="language-rust">fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}
</code></pre>
<p>This is our first Rust function.</p>
<p>Breaking it apart into bits and pieces:</p>
<pre><code class="language-rust">fn click_damage(base: u32, bonus: u32) -&gt; u32
</code></pre>
<p><code>fn</code> means we are defining a function.</p>
<p>The function name is:</p>
<pre><code class="language-text">click_damage
</code></pre>
<p>It takes two values:</p>
<pre><code class="language-rust">base: u32
bonus: u32
</code></pre>
<p><code>u32</code> is the type of both values. A type tells Rust what kind of data a value represents and what operations make sense for it.</p>
<p>The colon connects the parameter name to its type, so:</p>
<pre><code class="language-rust">base: u32
</code></pre>
<p>means that <code>base</code> is a value with a type of <code>u32</code>.</p>
<p>For now, think of <code>u32</code> as a non-negative whole number. More specifically, it is an unsigned 32-bit integer.</p>
<p>So these are valid <code>u32</code> values:</p>
<pre><code class="language-text">0
1
25
1000
</code></pre>
<p>But this is not:</p>
<pre><code class="language-text">-5
</code></pre>
<p>After the parameters, we have:</p>
<pre><code class="language-rust">-&gt; u32
</code></pre>
<p>This tells Rust that the function returns a value whose type is also <code>u32</code>.</p>
<p>Then:</p>
<pre><code class="language-rust">{
    base.saturating_add(bonus)
}
</code></pre>
<p>is the value we return.</p>
<p>Rust lets the last expression in a function become its return value by leaving off the semicolon.</p>
<p>So:</p>
<pre><code class="language-rust">fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}
</code></pre>
<p>means:</p>
<blockquote>
<p>Take the player's base damage and bonus damage, add them together, and return the result.</p>
</blockquote>
<hr />
<h2>Why not just use <code>base + bonus</code>?</h2>
<p>We could. For most ordinary values, both versions give us the same answer. However, <code>u32</code> has a maximum value.</p>
<p><code>saturating_add()</code> gives our game explicit behavior at that boundary: damage stops at the largest value <code>u32</code> can represent instead of leaving overflow behavior to the build configuration.</p>
<p>This is a <strong>game-design choice</strong>, not a rule that Rust programs should always use saturating arithmetic.</p>
<p>We are choosing it because damage is a quantity where hitting the numeric ceiling makes more sense than allowing overflow.</p>
<hr />
<h2>Call the function</h2>
<p>Now we can give <code>main()</code> something to do:</p>
<pre><code class="language-rust">fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn main() {
    let damage = click_damage(5, 2);

    println!("You dealt {damage} damage!");
}
</code></pre>
<p>Run:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">You dealt 7 damage!
</code></pre>
<p>Technically, we did not escape <code>println!()</code> after all.</p>
<p>But at least we are using it to deal some damage as promised.</p>
<hr />
<h2>Give the enemy some health</h2>
<p>Damage is not useful if our enemies do not have a health pool.</p>
<p>Our second rule will calculate how much health remains after an attack:</p>
<pre><code class="language-rust">fn health_after_hit(health: u32, damage: u32) -&gt; u32 {
    health.saturating_sub(damage)
}
</code></pre>
<p>Notice how this looks a lot like our first function.</p>
<p>It takes:</p>
<pre><code class="language-rust">health
damage
</code></pre>
<p>and returns the remaining health.</p>
<p>Again, we are using saturating arithmetic.</p>
<p>Why?</p>
<p>Imagine the enemy has:</p>
<pre><code class="language-text">3 health
</code></pre>
<p>and we deal:</p>
<pre><code class="language-text">10 damage
</code></pre>
<p>For our game, we want the result to be:</p>
<pre><code class="language-text">0
</code></pre>
<p>not:</p>
<pre><code class="language-text">-7
</code></pre>
<p>Health cannot drop below zero.</p>
<p>So:</p>
<pre><code class="language-rust">health.saturating_sub(damage)
</code></pre>
<p>expresses this game rule clearly.</p>
<p>Try it:</p>
<pre><code class="language-rust">fn main() {
    let damage = click_damage(5, 2);
    let remaining_health = health_after_hit(10, damage);

    println!("You dealt {damage} damage!");
    println!("Enemy health: {remaining_health}");
}
</code></pre>
<p>Run again:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">You dealt 7 damage!
Enemy health: 3
</code></pre>
<p>Now we are starting to have something that resembles combat.</p>
<p>Barely. But it counts.</p>
<hr />
<h2>Is the enemy defeated?</h2>
<p>Our third rule is even smaller:</p>
<pre><code class="language-rust">fn is_defeated(health: u32) -&gt; bool {
    health == 0
}
</code></pre>
<p>This introduces another Rust type:</p>
<pre><code class="language-text">bool
</code></pre>
<p>A boolean has two possible values:</p>
<pre><code class="language-text">true
false
</code></pre>
<p>Our function answers one question:</p>
<blockquote>
<p>Has the enemy's health reached zero?</p>
</blockquote>
<p>If we call:</p>
<pre><code class="language-rust">is_defeated(0)
</code></pre>
<p>we get:</p>
<pre><code class="language-text">true
</code></pre>
<p>If we call:</p>
<pre><code class="language-rust">is_defeated(10)
</code></pre>
<p>we get:</p>
<pre><code class="language-text">false
</code></pre>
<p>It is time to use it.</p>
<pre><code class="language-rust">fn main() {
    let enemy_health = 5;

    let damage = click_damage(5, 2);
    let remaining_health =
        health_after_hit(enemy_health, damage);

    println!("You dealt {damage} damage!");
    println!("Enemy health: {remaining_health}");

    if is_defeated(remaining_health) {
        println!("Enemy defeated!");
    }
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">cargo run
</code></pre>
<p>and now we get:</p>
<pre><code class="language-text">You dealt 7 damage!
Enemy health: 0
Enemy defeated!
</code></pre>
<p>Now we have combat rules!</p>
<p>Tiny ones. But real ones.</p>
<hr />
<h2>Why make these separate functions?</h2>
<p>We could have written everything directly inside <code>main()</code>:</p>
<pre><code class="language-rust">fn main() {
    let health = 5;
    let base_damage = 5;
    let bonus_damage = 2;

    let damage = base_damage + bonus_damage;
    let remaining_health =
        health.saturating_sub(damage);

    if remaining_health == 0 {
        println!("Enemy defeated!");
    }
}
</code></pre>
<p>That works. Especially for a program this small, and it may even look easier.</p>
<p>But this game is not going to stay this small.</p>
<p>Eventually we will need to answer questions like:</p>
<ul>
<li>How much damage does the player deal?</li>
<li>Did the enemy die?</li>
<li>How much gold should the player receive?</li>
<li>How much XP did they earn?</li>
<li>Did they level up?</li>
<li>How strong should the next enemy be?</li>
<li>How much passive damage happened while the player was away?</li>
</ul>
<p>If all of those rules live inside one giant function, the program gets harder to understand very quickly.</p>
<p>Instead, we are starting with small pieces that have clear jobs:</p>
<pre><code class="language-text">click_damage()
health_after_hit()
is_defeated()
</code></pre>
<p>Later, bigger functions will use these functions.</p>
<p>Then even bigger behavior will use those.</p>
<p>That is how we are going to build the entire game.</p>
<p>Not by writing one giant clever function.</p>
<p>By combining small, boring ones until the result is no longer boring.</p>
<hr />
<h2>Testing the rules</h2>
<p>These functions have another useful property:</p>
<p>They are easy to test.</p>
<p>We give them input.</p>
<p>We check the output.</p>
<p>At the bottom of <code>main.rs</code>, add:</p>
<pre><code class="language-rust">#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }
}
</code></pre>
<p>Run:</p>
<pre><code class="language-bash">cargo test
</code></pre>
<p>You should see three passing tests.</p>
<p>We are not going to turn this into a giant testing lesson yet.</p>
<p>For now, notice how easy it is to verify a small function with a clear input and output.</p>
<p>That will matter more as the game grows.</p>
<hr />
<h2>The complete program</h2>
<p>At this point, <code>src/main.rs</code> should look like this:</p>
<pre><code class="language-rust">fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(
    health: u32,
    damage: u32,
) -&gt; u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -&gt; bool {
    health == 0
}

fn main() {
    let enemy_health = 5;

    let damage = click_damage(5, 2);

    let remaining_health =
        health_after_hit(enemy_health, damage);

    println!("You dealt {damage} damage!");
    println!("Enemy health: {remaining_health}");

    if is_defeated(remaining_health) {
        println!("Enemy defeated!");
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }
}
</code></pre>
<p>This is not much of a game yet.</p>
<p>And that is okay.</p>
<p>We now have something more useful than a big pile of code, and we have three rules that we understand.</p>
<hr />
<h2>What we learned</h2>
<p>Along the way, we have already encountered:</p>
<ul>
<li><p><code>fn</code></p>
</li>
<li><p>function parameters</p>
</li>
<li><p>return types</p>
</li>
<li><p><code>u32</code></p>
</li>
<li><p><code>bool</code></p>
</li>
<li><p><code>let</code></p>
</li>
<li><p>expressions</p>
</li>
<li><p><code>if</code></p>
</li>
<li><p>function calls</p>
</li>
<li><p>Cargo</p>
</li>
<li><p>basic Rust tests</p>
</li>
</ul>
<p>You do not need to memorize all of that yet. More importantly, we can now describe combat with three small rules:</p>
<pre><code class="language-text">calculate damage
       ↓
reduce health
       ↓
check for defeat
</code></pre>
<p>That is our first little game system.</p>
<p>And we are keeping it.</p>
<hr />
<h2>Where we will take it next</h2>
<p>Right now <code>main()</code> is doing everything — calculating damage, applying it, checking for defeat. Those three steps together represent a single larger idea: an attack.</p>
<p>Next, we will build that concept as its own function, using the three small functions we already wrote. That is where this project starts to show its real shape.</p>
<p>Eventually a click in the browser will travel through a Web Component, into WebAssembly, and down into Rust — all the way to:</p>
<pre><code class="language-rust">fn click_damage(base: u32, bonus: u32) -&gt; u32 {
    base.saturating_add(bonus)
}
</code></pre>
<p>Small function. Bigger game.</p>
]]></content:encoded></item><item><title><![CDATA[What a Long, Strange Trip...]]></title><description><![CDATA[After a year away, I’m back with lessons from parenthood, Code the Dream, React, Node.js, testing, teamwork, and learning to keep coming back.]]></description><link>https://journeytocode.hashnode.dev/what-a-long-strange-trip</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/what-a-long-strange-trip</guid><category><![CDATA[Code the Dream]]></category><category><![CDATA[React]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[software development]]></category><category><![CDATA[Career]]></category><category><![CDATA[parenting]]></category><category><![CDATA[Testing]]></category><category><![CDATA[chingu]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sat, 19 Sep 2026 14:57:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65ece53114d8768ce885db88/85b51a68-ebf3-48cf-a62a-3f5def42f382.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>The last time I posted was on April 28, 2025. I know the exact date because it was the same day I got married.</p>
<p>Looking back, that might explain why this blog settled down soon after.</p>
<p>Not because I stopped programming. I was still building, learning, and working through plenty of code. I just quit writing <em>about</em> it.</p>
<p>As time passed, the blog seemed to drift further down the backlog. When I found myself with some free time, I was more likely to spend it squashing bugs, working on a project, or learning something new than writing about any of it.</p>
<p>So before I start writing about what comes next, I should probably explain what has happened since then.</p>
<p>Because it was a year.</p>
<h2>First, a tiny detail I forgot to mention</h2>
<p>While participating in a <a href="https://chingu.io">Chingu</a> Voyage, I met my wife.</p>
<p>Yes, <em>that</em> Chingu.</p>
<p>The online platform that put together teams of software engineers and agile leaders to build projects suitable to their abilities, argue politely about Git branches, and every once in a while spend twenty minutes figuring out who forgot to pull before opening a PR.</p>
<p>Before this past year, I had completed several Chingu Voyages, working as a developer and a product owner, and even as a mentor. One of those Voyages just happened to work out particularly well.</p>
<p>I met her.</p>
<p>As far as I am aware, almost nobody over there knows about it. It never really came up, and there never seemed to be a natural moment in a Discord channel to mention it. Plus it all escalated well after that Voyage ended.</p>
<p>This blog has always been about my experience writing code and sharing the lessons I've learned in order to help other developers grow and tap into their potential.</p>
<p>So this may seem like it comes out of left field, but Chingu gave me teammates, portfolio projects, experience working on software teams...</p>
<p>...and a wife.</p>
<p>Maybe not in that order.</p>
<p>You might say that it was a successful Voyage.</p>
<h2>Before the Baby, There Was Baby Book</h2>
<p>Before summer could even arrive, we found out our lives were about to change again!</p>
<p>We found out we were expecting a son, due November 7. We began to think about all of the little things that come with two people becoming parents.</p>
<p>Naturally, we both looked at each other and said:</p>
<blockquote>
<p>We could probably build an app for this.</p>
</blockquote>
<p>So we began working on an idea we called <strong>Baby Book</strong>.</p>
<p>In the first phase, it was only a household task and grocery list tracking app. The long-term plan was to track milestones, achievements, appointments, and eventually the little things one parent might miss while the other was there—so we could stay on the same page.</p>
<p>More or less, we set out to build a shared record of our new favorite person's life as it began to unfold.</p>
<p>We never did polish the application to the original design plan, but for a little while it was actually useful.</p>
<p>When she went into labor, I threw together a quick feature for helping her with breathing and contractions. When our son was born, Baby Book shifted toward the realities of parenthood. We started tracking feeding times and details, diaper changes, and little notes.</p>
<p>It did not take long before we did what most new parents probably do.</p>
<p>We fell into a rhythm and stopped stressing over those details as much.</p>
<p>Our need for this app faded, and Baby Book quietly moved into the category titled:</p>
<blockquote>
<p>I should really come back to this someday.</p>
</blockquote>
<p>And I still may.</p>
<p>Some of the original idea could still be useful today, especially around milestones and giving us an easy way to share the little things the other parent might miss.</p>
<p>But it is not exactly at the top of the backlog right now.</p>
<h2>A Wild Son Has Appeared</h2>
<p>Our son was supposed to arrive on <strong>November 7</strong>.</p>
<p>Apparently, he had his own release schedule. He arrived <strong>October 12</strong>, about four weeks early.</p>
<p>Anybody who has had an infant can probably relate to what happened to my schedule next. If you haven't, imagine handing your calendar to a tiny version of yourself that only speaks babble, uses his mouth to read, and thinks 3:17 in the morning is the perfect time to discuss politics.</p>
<p>That is roughly the system, right?</p>
<p>The funny part is that another fairly important date had happened only five days earlier.</p>
<p>On <strong>October 7</strong>, I was accepted into Code the Dream's React course.</p>
<h2>Code the Dream Was Already in Motion</h2>
<p>I had actually applied to <a href="https://codethedream.org/"><strong>Code the Dream</strong></a> months earlier, around April 2025.</p>
<p>The timing of the whole thing could have been a little better. But it is not like I brought the baby home, learned a thing or two about our new tiny boss, and decided:</p>
<blockquote>
<p>You know what this situation needs?</p>
</blockquote>
<blockquote>
<p>Another commitment.</p>
</blockquote>
<p>The decision had already been made.</p>
<p>I had tested out of the Intro to Programming course, which is how I ended up going directly into React.</p>
<p>By this point, I knew that it was going to be a challenge, but it seemed reasonable enough.</p>
<p>Strangely enough, his early arrival may actually have made the start of the course easier. I lost some preparation time, but I gained a few weeks to settle into being a parent before React started. And that made a big difference.</p>
<p>By the time the React cohort started, I was not deciding whether I wanted to begin another programming course while raising a newborn. I had already committed to the course.</p>
<p>Now I just had to figure out what continuing looked like under completely different circumstances.</p>
<p>The answer was very different from the way I had worked before.</p>
<p>Long, uninterrupted coding sessions became harder to find. Development started happening in smaller windows: thirty minutes here, an hour there, whatever time happened to exist between everything else.</p>
<p>If twenty minutes of a thirty-five-minute session were spent trying to remember what I had been doing the last time I opened the project, that was a problem.</p>
<p>So I started leaving myself better notes. I broke problems into smaller pieces. I became more deliberate about commits and less interested in keeping an entire feature in my head.</p>
<p>Without really planning to, becoming a parent forced me to get better at stopping halfway through something while leaving enough breadcrumbs that future me could figure out what past me had been doing.</p>
<p>And then React started.</p>
<h2>React, the Second Time Around</h2>
<p>I joined Code the Dream's React v3 course as part of the <strong>Lark Cohort</strong>, which ran from November through February.</p>
<p>Code the Dream was not where I first learned React, or where I first built a full-stack application. I had already built MERN applications, worked with APIs, Express, and MongoDB, and completed several development projects with other people through Chingu.</p>
<p>What CTD gave me was something different.</p>
<p>It gave me several months to go back through technologies I already knew and find the places where my understanding was thinner than I realized.</p>
<p>One of those places was <strong>Context</strong>.</p>
<p>I had used Context before and understood what it did, but there is a difference between knowing that Context exists and getting comfortable designing state around it, combining it with reducers, deciding what actually belongs there, and thinking about what happens as your component tree gets larger.</p>
<p>Sometimes learning something for the second time is a lot more useful than learning it the first time.</p>
<p>The first time, you are mostly trying to make it work.</p>
<p>The second time, you start asking why it works that way.</p>
<p>That became a recurring theme during the course. It was less:</p>
<blockquote>
<p>Here is React.</p>
</blockquote>
<p>and more:</p>
<blockquote>
<p>You know React. Now let's find the parts you have been getting away with not understanding deeply enough.</p>
</blockquote>
<p>My guided project grew throughout the course. Reducers came in, Context became more important, routing got more complicated, external data got involved, state crossed more boundaries, and testing became a larger part of the way I worked.</p>
<p>Eventually something I had built myself six weeks earlier could surprise me, which is always reassuring.</p>
<p>Somewhere during that process, the question I asked after completing something changed.</p>
<p>It used to be:</p>
<blockquote>
<p>Does it work?</p>
</blockquote>
<p>I would click around a few times, see that everything looked fine, and move on.</p>
<p>Now I am much more likely to ask:</p>
<blockquote>
<p>Okay, it works. What breaks it?</p>
</blockquote>
<p>What happens if the request fails? What happens if the user refreshes here? What if this state has not hydrated yet? What happens if the component disappears before an asynchronous operation finishes? What happens if somebody clicks the same button five times while the first request is still running?</p>
<p>The happy path is still important.</p>
<p>I just trust it a lot less than I used to.</p>
<h2>Then I Went Back Through the Backend</h2>
<p>After React, I moved into Code the Dream's <strong>Node 26.2 Cohort</strong>, which ran from late March through June.</p>
<p>Again, Node and Express were not new territory. I had already built full-stack applications and worked with the MERN stack, but going through a dedicated backend course gave me an opportunity to spend more time on the details instead of treating the backend as whatever needed to exist so the frontend could work.</p>
<p>We spent time with routes, middleware, authentication, validation, error handling, HTTP semantics, database access, and application structure.</p>
<p>One technology I got a lot more practice with was <strong>Prisma</strong>.</p>
<p>I had used Prisma before CTD, but not enough that I would have called myself particularly comfortable with it. CTD gave me more opportunities to use it deliberately instead of simply following the happy path through an ORM.</p>
<p>That distinction mattered.</p>
<p>There are a lot of technologies I have <em>used</em>. That does not necessarily mean I understand all of the decisions they are making for me.</p>
<p>Knowing how to use a tool is one level.</p>
<p>Knowing what that tool is abstracting away is another.</p>
<h2>Then Came Practicum</h2>
<p>After finishing the coursework, I joined Code the Dream's <strong>Summer 2026 Practicum</strong>.</p>
<p>This part felt familiar.</p>
<p>I had already done several team projects through Chingu, so practicum was not my introduction to collaboration, Git, pull requests, requirements, product decisions, or the occasional joy of discovering two developers had spent an afternoon independently solving the same problem.</p>
<p>What made practicum interesting was the amount of structure we had to build around the development itself.</p>
<p>Our team built <a href="https://github.com/Code-the-Dream-School/summer-26-js-practicum-team2"><strong>Sprout</strong></a>, a financial literacy application.</p>
<p>The easiest way I have found to describe it is something like Duolingo pointed at personal finance: short lessons, quizzes, progress tracking, onboarding, and a guided learning path.</p>
<p>The idea was to make a subject people know they <em>should</em> understand feel less like sitting down with a textbook, because almost nobody wakes up Saturday morning thinking:</p>
<blockquote>
<p>You know what would really make this weekend? Budgeting terminology.</p>
</blockquote>
<p>Sprout was not the project I would have chosen on my own, and I think that made the experience more interesting.</p>
<p>When something is your personal project, you can build whatever you want. On a team, the product does not belong entirely to you. You have to build the thing the group agreed to build.</p>
<p>That is a different skill.</p>
<h2>Before We Built Much of Anything, We Built a Backlog</h2>
<p>One of the first things I did was start planning.</p>
<p>And then I planned some more.</p>
<p>And then apparently I decided Jira had not suffered enough, because I built a pretty enormous backlog.</p>
<p>There are still plenty of tickets sitting in it that we never touched, and that was intentional. A backlog is not a promise that everything gets built. It is a record of things that <em>could</em> get built.</p>
<p>We split the core work across sprint weeks and broke larger ideas into epics, user stories, and smaller tasks. A Jira ticket would often become a GitHub issue. That issue gave us something concrete to build around and could then map to a branch.</p>
<p>Eventually there was a clear line connecting:</p>
<p><strong>idea → epic → story → issue → branch → pull request → application</strong></p>
<p>That process became one of the more valuable parts of practicum for me because a large application does not get built by somebody opening an editor and thinking:</p>
<blockquote>
<p>Today I shall implement the application.</p>
</blockquote>
<p>You build a small piece. Then another. Then another.</p>
<p>Sometimes several developers were contributing to the same larger feature, so we would use what amounted to a parent or integration branch. Instead of everybody immediately merging their work into <code>development</code>, we could combine related work there first, get the pieces cooperating, fix whatever exploded, and then merge the larger feature.</p>
<p>It added another layer to the Git workflow, but it also prevented <code>development</code> from becoming quite as adventurous.</p>
<h2>We Never Really "Finished" an Epic</h2>
<p>Something else became apparent pretty quickly: epics are big.</p>
<p>Really big.</p>
<p>When you only have a limited number of weeks, "complete the epic" is often the wrong goal. So we sliced useful pieces out of them instead.</p>
<p>One week we might improve onboarding. Later, we would come back and add something else. The learning path grew incrementally. Admin tools grew incrementally. Accounts grew incrementally. Testing grew incrementally.</p>
<p>Even now, I do not think I could point at many of our larger epics and say:</p>
<blockquote>
<p>Yep. Finished forever.</p>
</blockquote>
<p>There are still stories sitting in the backlog, but the application works.</p>
<p>That taught me something useful about MVP development. An epic does not have to be complete for part of it to create value. Sometimes the right feature is simply the smallest useful slice of a much larger idea.</p>
<p>In a strange way, I suppose Baby Book had already taught me a smaller version of that lesson.</p>
<p>It never became the application I originally imagined, but for a brief period the pieces we actually needed were useful.</p>
<p>Sometimes software has already done its job before you finish everything you once imagined for it.</p>
<h2>Then Sprout Became Complicated</h2>
<p>Sprout used React on the frontend, Node and Express on the backend, and MongoDB underneath. We used Tailwind CSS and eventually created a collection of shared components so the UI could stay relatively consistent while different developers worked on different screens.</p>
<p>That became important very quickly.</p>
<p>Without shared components, five developers can somehow invent seven buttons. Each one is slightly different; none of them are necessarily wrong, and together they make the application look like it was assembled by committee.</p>
<p>Because it was.</p>
<p>Shared components gave us some guardrails, but the more interesting architectural decisions happened around the curriculum.</p>
<p>Several members of the team worked together to build our learning material into structured JSON, and a huge part of the application eventually revolved around that structure: lessons, sections, quizzes, questions, answers, and progress.</p>
<p>The JSON became something close to a content engine for the application.</p>
<p>For a while, that worked really well.</p>
<p>Then we started asking uncomfortable questions. Questions like:</p>
<blockquote>
<p>Should the correct answers to a quiz really be sitting somewhere the browser can inspect?</p>
</blockquote>
<p>Probably not.</p>
<h2>One Architecture Decision Created a Whole New Category of Bugs</h2>
<p>Eventually we started integrating more of the curriculum data with MongoDB.</p>
<p>Part of the motivation was protecting quiz answers. If the browser receives everything—including which answer is correct—then somebody who knows their way around developer tools can potentially skip the whole "learning" portion of the learning application.</p>
<p>Which feels slightly against the spirit of the thing.</p>
<p>Moving more responsibility to the backend made sense, but architectural decisions rarely arrive alone. This one brought friends: more requests, more synchronization, more state, more opportunities for the frontend and backend to disagree, performance concerns, and some really annoying bugs.</p>
<p>We solved one problem and introduced an entirely different class of problems.</p>
<p>That does not necessarily mean the decision was wrong. It means the decision had a cost, and that might be one of the most useful things Sprout reinforced for me.</p>
<p>Architecture is usually not:</p>
<blockquote>
<p>Which solution is correct?</p>
</blockquote>
<p>It is more often:</p>
<blockquote>
<p>Which set of problems would you rather have?</p>
</blockquote>
<p>I may revisit that part of Sprout in the future because I think there are better ways we could protect assessment data without making parts of the experience as expensive or fragile as they became.</p>
<p>But that is also why I am glad we tried it.</p>
<p>You learn a lot more from an architectural decision that creates unexpected problems than one that only exists in a diagram.</p>
<h2>Testing Was Only Half of What I Enjoyed</h2>
<p>Testing became a major part of my role on the project. I worked with <strong>Jest, Vitest, React Testing Library, and Playwright</strong>, and I liked the way testing forced us to think beyond the exact sequence of events we pictured when writing a feature.</p>
<p>What happens if the component unmounts? What happens if the network request fails? What if authentication has not finished hydrating? What if somebody double-clicks? What if the frontend thinks a lesson is complete and the backend disagrees?</p>
<p>We found all of those.</p>
<p>But something else ended up being just as interesting to me:</p>
<p><strong>CI.</strong></p>
<p>I really enjoyed working with <strong>GitHub Actions</strong> and getting our tests running automatically as part of the development workflow.</p>
<p>There is something deeply satisfying about turning:</p>
<blockquote>
<p>Somebody should probably run the tests before we merge this.</p>
</blockquote>
<p>into:</p>
<blockquote>
<p>The repository literally will not let us forget.</p>
</blockquote>
<p>That felt like another step beyond simply knowing how to write tests.</p>
<p>A test suite sitting on somebody's laptop is useful. A test suite that runs automatically against changes is infrastructure.</p>
<p>Once I started working on that side of the project, I found myself liking it quite a bit: tests, coverage, automated checks, GitHub Actions, deployment workflows, and all the little pieces that make it harder for a team to accidentally ship something broken.</p>
<p>None of those things are particularly flashy.</p>
<p>But they make everything else easier to trust.</p>
<h2>Some Bugs Deserve Their Own ZIP Code</h2>
<p>We still had plenty of bugs.</p>
<p>Some were straightforward, friendly bugs. The sort that basically introduce themselves:</p>
<blockquote>
<p>Hello. I am on line 47.</p>
</blockquote>
<p>Those are wonderful.</p>
<p>Others required the application to be in exactly the wrong state at exactly the right time.</p>
<p>We had authentication behavior where the frontend could decide where a user should be redirected before it had finished determining whether the user was authenticated. Everything worked unless the timing changed, which is always comforting.</p>
<p>We had asynchronous quiz operations where work could finish after the component that started it no longer existed. We had buttons users could click multiple times while a request was still running. We had lesson progression and quiz completion logic with occasionally incompatible definitions of reality.</p>
<p>Some of those bugs looked completely fine when you clicked through the application manually.</p>
<p>Then an automated test created the right sequence of events and everything fell apart.</p>
<p>Beautiful.</p>
<p>Those are the problems I enjoy the most now: the ones where the solution requires understanding the system rather than simply finding a typo.</p>
<h2>Git Got More Interesting Too</h2>
<p>Working by yourself gives you a very misleading impression of Git.</p>
<p>You make a branch, write some code, commit it, merge it, and suddenly you feel pretty good about version control.</p>
<p>Working with several developers changes the situation, especially once branches start depending on other branches.</p>
<p>Suddenly Git becomes less:</p>
<blockquote>
<p>Save my work.</p>
</blockquote>
<p>and more:</p>
<blockquote>
<p>Which version of this application represents reality right now?</p>
</blockquote>
<p>The parent-branch strategy helped with that. So did smaller pull requests, better issues, and simply talking to each other before two people changed the same area of the application.</p>
<p>That is the funny thing about Git problems.</p>
<p>A surprising number of them are actually communication problems that happen to eventually produce merge conflicts.</p>
<h2>Practicum Felt Familiar Because of Chingu</h2>
<p>A lot of practicum reminded me of <strong>Chingu Voyages</strong>, which makes sense because I had already done several Voyages as both a developer and a Product Owner.</p>
<p>Chingu had already taught me that two developers can read the same requirement and walk away with completely different applications in their heads. It had already shown me how important communication becomes once multiple people share a repository, and it had already exposed me to team roles and product planning.</p>
<p>The two programs just approached those experiences differently.</p>
<h2>Chingu Does a Great Job Simulating a Product Team</h2>
<p>One thing I genuinely like about Chingu is that a Voyage can include several different roles.</p>
<p>Developers may work alongside a <strong>Product Owner, Scrum Master, and UI/UX Designer</strong>, which creates some separation between responsibilities. A designer is concentrating on how something should look and behave. A Product Owner is shaping priorities and requirements. A Scrum Master helps keep the process moving.</p>
<p>Developers then have to take those ideas and make them real.</p>
<p>And eventually everybody discovers that a button somebody drew in thirty seconds somehow requires half a day to implement correctly.</p>
<p>That structure is useful.</p>
<p>The challenge, particularly in some lower tiers, is that experience levels among developers can vary quite a bit. Everybody might technically satisfy the requirements for the Voyage while having very different levels of comfort with JavaScript, Git, architecture, APIs, or debugging.</p>
<p>Sometimes you discover those differences while trying to ship something.</p>
<p>Which, to be fair, is also pretty realistic.</p>
<h2>Code the Dream Gave Us a Different Baseline</h2>
<p>Code the Dream's practicum had another advantage: everyone had already completed defined coursework.</p>
<p>That did not make everyone's skills identical—not even close—but it gave us shared reference points. We had all traveled through at least some of the same material before arriving at the project.</p>
<p>The bigger difference was that we did not have every traditional product role separated out. We were mostly developers, so we had to wear more hats ourselves.</p>
<p>Someone had to think about product. Someone had to think about design. Someone had to organize the work and maintain the backlog. Someone had to worry about whether CI was working.</p>
<p>Usually everybody ended up doing more than whatever role they originally imagined themselves doing.</p>
<p>And I think that was good for us.</p>
<h2>The People Part</h2>
<p><a href="https://www.linkedin.com/in/berenice-rojas"><strong>Berenice</strong></a> stepped in beside me as a co-project lead, and that helped me more than I probably said at the time. She even played a role in designing the UI/UX.</p>
<p>She gave me somebody to bounce ideas off of while we built epics, shaped user stories, created tasks, organized Jira, and tried to decide which pieces actually mattered enough to belong in our MVP.</p>
<p>Berenice and I worked together to decide which parts were actually critical for an MVP. Those moved forward; the rest could wait.</p>
<p>There are a lot of small judgment calls hidden inside project planning. Having another person there to challenge an idea—or confirm that I was not completely overthinking it—made that a lot easier.</p>
<p><a href="http://www.linkedin.com/in/kristen-wishart"><strong>Kristen</strong></a> and <a href="https://github.com/BytesOfStrength"><strong>Maryzabeth</strong></a> took on another enormous part of the application.</p>
<p>They worked together on our initial low-fidelity wireframes, which became high-fidelity wireframes and eventually the foundation for the UI and UX. Then they worked together on developing the curriculum that became the content driving much of Sprout.</p>
<p>Kristen had a particularly good instinct for keeping us connected to the original vision. Software developers are exceptionally talented at taking a reasonable idea and turning it into fourteen features, three dashboards, a notification system, and possibly a social network.</p>
<p>Sometimes somebody has to say:</p>
<blockquote>
<p>We probably do not need that.</p>
</blockquote>
<p>Kristen was good at that. A lot of her vision made it into the final product precisely because she was willing to pull us back toward the application we had actually agreed to build.</p>
<p>Maryzabeth became one of those people every remote team quietly depends on. She contributed across both frontend and backend work, but she also became our unofficial secretary.</p>
<p>She took notes, documented decisions, and if somebody missed a meeting or standup, she was usually the person making sure they knew what happened.</p>
<p>That sounds like a small thing until you work on a distributed team.</p>
<p>Then you discover that keeping everybody operating from the same version of reality is basically a feature of its own.</p>
<p>Everybody ended up bringing something beyond code, and that is the part of team development I find most interesting.</p>
<h2>We Also Had Actual Adults in the Room</h2>
<p>Code the Dream pairs practicum teams with mentors, and our team had <a href="https://www.linkedin.com/in/hectorfgonzalez/"><strong>Hector</strong></a> and <a href="https://www.linkedin.com/in/mntri4/"><strong>Mario</strong></a>.</p>
<p>Hector brought real-world software development experience into our conversations, while Mario brought a lot of optimism and was always willing to listen while we worked our way through something.</p>
<p>What helped most was rarely somebody simply handing us an answer. Usually it was somebody with more experience asking a question we had not asked ourselves:</p>
<blockquote>
<p>Why did you build it this way?</p>
</blockquote>
<p>Sometimes we had a very good answer.</p>
<p>Sometimes the answer turned out to be:</p>
<blockquote>
<p>Because that is how it currently is.</p>
</blockquote>
<p>Those conversations were useful too.</p>
<h2>So What Actually Changed?</h2>
<p>This is probably where I would describe the year differently than I would have twelve months ago.</p>
<p>I did not learn that teams exist. I had already worked on teams. I did not learn how to build a full-stack application. I had already built them. I did not discover MERN. I already knew it.</p>
<p>What changed was the level at which I started thinking about all of those things.</p>
<p>I understand React Context better than I did. I am much more comfortable with testing. I have more practical experience with Prisma. I learned more about CI and GitHub Actions than I expected to, and I got more interested in the infrastructure surrounding the code.</p>
<p>I spent more time thinking about how work gets broken down before anyone writes it. I became more deliberate about branch strategies when several pieces of one feature were being developed at the same time. I watched an architectural decision solve one problem while creating three new ones, and I saw how much a shared component library matters when several developers are building the same UI.</p>
<p>More than anything, I got another reminder that software development is full of tradeoffs disguised as decisions.</p>
<p>Maybe that is the biggest thing I got out of this year.</p>
<p>I did not necessarily add another stack to the list of technologies I know.</p>
<p>I got better at seeing the system around them.</p>
<h2>The Other Lesson Had Nothing to Do With Code</h2>
<p>I also learned that progress does not require the schedule I thought it did.</p>
<p>For a long time, I pictured serious development work happening in large, uninterrupted blocks: a free Saturday, several hours at a desk, headphones on, everything lined up.</p>
<p>That sounds wonderful.</p>
<p>It describes almost none of what the past year looked like.</p>
<p>A lot of this year happened in pieces. Thirty minutes here. An hour after bedtime. A bug before dinner. A pull request during nap time. Reading yesterday's notes because I had absolutely no idea what the version of me from forty-eight hours ago had been thinking.</p>
<p>And somehow it still added up.</p>
<p>I used to think real progress required long stretches of uninterrupted time. It turns out a surprising amount of it comes from getting good at returning.</p>
<p>Open the laptop. Find the note. Read the issue. Make the next change. Commit it. Come back later.</p>
<p>That is not the montage version of learning software development.</p>
<p>It is probably a lot closer to the real one.</p>
<h2>The Part That Made Any of This Possible</h2>
<p>Outside the project, my wife <a href="https://www.linkedin.com/in/jayanti-hari/"><strong>Jayanti</strong></a> listened to far more discussion about failed tests, authentication bugs, GitHub Actions, pull requests, MongoDB, Render deployments, and merge conflicts than any reasonable marriage contract should require.</p>
<p>She was also there through the much more important part of the year while we figured out how to become parents together.</p>
<p>That included the unfinished Baby Book, the last-minute contraction feature, sleep schedules that were mostly theoretical, and plenty of development sessions squeezed into whatever part of the day happened to survive.</p>
<p>It has been a year.</p>
<p>A really good one, a very busy one, and often a very tired one.</p>
<h2>So... What's Next?</h2>
<p>Practicum has wrapped.</p>
<p>I may keep tinkering with Sprout. There are still architectural decisions I want to revisit, experiments I want to try, and a Jira backlog large enough to keep several imaginary development teams busy.</p>
<p>But I am also ready to build other things.</p>
<p>I have a Rust game I keep coming back to. I want to contribute to open source properly. Baby Book is still sitting somewhere in the back of my head, too.</p>
<p>And I have a growing list of things from this past year that deserve articles of their own: CI pipelines, testing patterns, authentication races, architecture mistakes, Prisma, React, Rust, and the sort of problems where the first draft of the article usually begins with:</p>
<blockquote>
<p>I thought this would be easy.</p>
</blockquote>
<p>Those are usually the interesting ones.</p>
<p>I have also applied to <strong>Code the Dream's apprenticeship program</strong>.</p>
<p>I do not know yet whether I will get in, and strangely, I am okay leaving that part of the story there.</p>
<p>Not knowing.</p>
<p>Because that is actually where I am.</p>
<p>There is a tendency when writing about learning software development to wait until you know how the story ends. Get the job. Finish the project. Earn the certificate. Then go back and write the neat version where everything looks like it was leading somewhere obvious.</p>
<p>Real life rarely gives you that version while you are living it.</p>
<p>Right now, I have finished one thing and I am waiting to find out what comes next. Maybe that is the apprenticeship. Maybe it is a job. Maybe it is open source.</p>
<p>Maybe I disappear into Rust for six months and emerge with something deeply unnecessary.</p>
<p>There are worse outcomes.</p>
<p>What I do know is that I finally want to write again, and I have a lot more to write about than I did when this blog went quiet.</p>
<p>So <em>Journey to Code</em> is back.</p>
<p>Probably a little slower. Definitely with more interruptions.</p>
<p>But I think I am okay with that now.</p>
<p>The last year taught me that progress does not always look like moving quickly. Sometimes it just means you keep coming back.</p>
<p>And, conveniently, I just did.</p>
]]></content:encoded></item><item><title><![CDATA[From DOS to Desktop Disasters]]></title><description><![CDATA[A humorous journey from early computer days to modern tech mishaps, exploring growth, family dynamics, and a lifelong passion for technology]]></description><link>https://journeytocode.hashnode.dev/from-dos-to-desktop-disasters</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/from-dos-to-desktop-disasters</guid><category><![CDATA[pc revival]]></category><category><![CDATA[trial and error]]></category><category><![CDATA[90sTech]]></category><category><![CDATA[CustomPCBuilding]]></category><category><![CDATA[AngelFire]]></category><category><![CDATA[PC build]]></category><category><![CDATA[tech journey]]></category><category><![CDATA[DOS]]></category><category><![CDATA[Doom]]></category><category><![CDATA[apache]]></category><category><![CDATA[Emulation]]></category><category><![CDATA[Personal growth  ]]></category><category><![CDATA[personal development]]></category><category><![CDATA[Windows]]></category><category><![CDATA[technology]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Mon, 28 Apr 2025 04:31:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745814245619/89f67cbd-32a7-4a68-bef8-ea48db5a51a1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I grew up as the youngest offspring of one particularly tech-savvy individual who among many jobs, initially moved our family from many places like Texas, Florida, North Carolina, and eventually to West Virginia where I was raised as a child. The area in particular that I grew up is very rural, and essentially a decade or more behind modern times. However in our household this was almost uncertainly not the case. Since my father was such a connoisseur of technology, it's basically an inherited trait to me. For a little bit of a backstory, we wound up here as my father was writing assembly language code that would help facilitate the operation of coal power plants. So, naturally we always had the latest and greatest trends.</p>
<p>Because of the available computing power at my fingertips I was often times able to experiment well before I should have with hardware. In our family, my dad would always upgrade his PC almost annually to the next latest and greatest system. One of my very first computers was a 286 processor with 16mhz. This introduced me to the world of the command line. I learned early how to navigate and traverse folders using Disk Operating System (or DOS). I learned the <code>del *.*</code> command at some point. At least one time or two, this got me into trouble. At home, it wasn't as big of a deal. After deleting DOS from my root directory once or twice, my dad decided to install his own standalone operating system for me that never let me reach the command line, instead I could only pick what game to play and it would type the commands for me.</p>
<h2 id="heading-the-tech-hand-me-down-dynasty">The Tech Hand-Me-Down Dynasty</h2>
<p>Our household had its own tech ecosystem, with my dad at the top of the pyramid. He'd get the cutting-edge system, then his previous machine would go to my oldest brother, whose computer would then go to my middle brother, which meant I'd inherit whatever my middle brother was using. Being the youngest in a tech-obsessed family had its perks, even if I was always three generations behind the curve.</p>
<p>When Dad upgraded to his first Pentium running at a mind-blowing 100MHz, it kicked off a beautiful cascade of hand-me-downs: oldest brother got Dad's 486 DX2 with 66MHz, middle brother received the 486DX with 33MHz, and I finally graduated to the 386 with 33MHz. Each time this happened, it felt like Christmas morning, even though I was essentially getting the technological equivalent of my brothers' worn-out sneakers.</p>
<p>Living in a rural West Virginia town where the public schools didn't even connect to the internet until after 2000, our home stood out like a digital oasis in a technological desert. My friends would visit and stare in wonder at our setup. Their jaws would hit the floor when they realized we had dedicated phone lines just for internet—a luxury in the era of dial-up when most families had to choose between making calls or going online.</p>
<p>Dad wasn't just tech-savvy; he ran a dial-up internet service provider. This meant we were always on the cutting edge, while my school was still using computers that might as well have been powered by hamster wheels. I never once experienced the frustration of having someone pick up the phone while I was online—a common trauma for most 90s kids.</p>
<h2 id="heading-doom-lan-parties-and-the-cruel-reality-of-crashed-map-editors">DOOM, LAN Parties, and the Cruel Reality of Crashed Map Editors</h2>
<p>The mid-90s were the golden age of DOOM, and our basement became command central for epic LAN parties around 1995. Back then, Windows couldn't hold a candle to DOS when it came to gaming performance. We would gather, connect our machines, and blast demons (or each other in deathmatch) for hours on end.</p>
<p>But we weren't just players—we were creators too. The DOOM modding scene was where I cut my teeth on level design, spending countless hours crafting custom maps... only to have my underpowered 386 crash mid-creation, erasing everything I'd done. I can still feel that particular brand of heartbreak that comes from losing hours of creative work to the digital void. Nothing teaches you to save your work like losing an entire afternoon's worth of meticulously placed monsters and secret rooms.</p>
<p>Storage in our house was a sight to behold. We literally had hundreds of 5.25" floppy disks stashed everywhere—loaded with games, programming languages, and applications from computing's early days. These massive, actually-floppy disks are probably still boxed up somewhere in my parents' house, a physical museum of software history.</p>
<p>I still remember the technological disappointment of our first 1x CD-ROM drive. On paper, it could hold way more data than any floppy. In practice? I discovered firsthand that it was often faster to install Windows using 64 separate 1.44" floppy disks than waiting for that sluggish CD to do its thing. The only advantage was being able to start the installation and walk away instead of sitting there feeding in disk after disk like some sort of human vending machine.</p>
<h2 id="heading-the-windows-product-key-savant">The Windows Product Key Savant</h2>
<p>Here's a weird party trick I still have: I can recite the Windows 95 and 98 product keys from memory. Seriously. After installing these operating systems dozens of times (usually after breaking something), those alphanumeric sequences are permanently etched into my brain alongside my social security number and childhood phone number.</p>
<p>Was it strictly kosher to use the same key repeatedly? Probably not. But in those days, activation was simpler, and we were just kids excited to get our machines back up and running after whatever catastrophe had befallen them (usually self-inflicted).</p>
<h2 id="heading-accidental-webmaster-and-the-great-apache-incident">Accidental Webmaster and the Great Apache Incident</h2>
<p>While most kids were content playing Nintendo, my brothers and I were diving headfirst into the wild west of early web development. We honed our skills on Geocities and AngelFire, painstakingly writing HTML in Notepad and proudly publishing pages that were essentially 99% animated GIFs and "Under Construction" banners.</p>
<p>When Dad discovered our interest, he kicked things up several notches—purchasing us a domain name, setting up a web server, and giving each of us our own subdomain. Suddenly we graduated from Notepad to the "professional-grade" Microsoft FrontPage and were learning about mysterious concepts like FTP and hosting. These were the early days of web development. JavaScript was not nearly as powerful as it has become.</p>
<p>One of my more notorious tech moments came when I accidentally installed Apache on my PC and started hosting a web server without even realizing what I'd done. Dad went into full-on security panic mode, convinced we'd been hacked, only to discover it was just his clueless youngest son experimenting with software he didn't understand. When he demanded to know why I'd done it, I genuinely had no idea what he was talking about—I didn't even know what Apache was! I'd just clicked through some install prompts and suddenly our home network had an unexpected web server. The digital equivalent of accidentally building a shed in the backyard.</p>
<h2 id="heading-emulation-console-gaming-on-pc-terms">Emulation: Console Gaming on PC Terms</h2>
<p>While my friends were saving up allowance money for GameBoy cartridges and Super Nintendo games, we were exploring the fascinating world of emulation. Why play Super Mario on a tiny GameBoy screen when you could play it on a computer monitor with the ability to save anywhere and apply cheats without those clunky GameShark devices?</p>
<p>It wasn't that we didn't have consoles—our gaming collection included everything from ancient Ataris and Commodore 64s to Nintendo, Super Nintendo, Genesis, Sega CD, and N64. But the flexibility of emulation gave us superpowers that console gamers could only dream of.</p>
<p>The crown jewel of our portable gaming collection wasn't even a Nintendo product—it was the Sega Nomad, a handheld that played full Genesis cartridges with a backlit screen when that was basically unheard of technology. This thing was truly revolutionary—a fully backlit portable console playing 16-bit games when the GameBoy was still rocking monochrome graphics and requiring external light sources.</p>
<h2 id="heading-breaking-free-my-first-custom-build">Breaking Free: My First Custom Build</h2>
<p>Fast forward to my teenage years. I was finally ready to break free from the hand-me-down cycle and build my very first PC from scratch. Spoiler alert: it didn't go smoothly.</p>
<p>My first motherboard purchase ended with the vendor vanishing faster than my motivation to do homework. Still salty about that one, but it taught me an early lesson about researching vendors before handing over cash.</p>
<p>Eventually, I assembled what felt like a supercomputer at the time: a Pentium 4 Dual Core running at 1.2GHz paired with some NVIDIA graphics card whose model number has long since faded from memory. But it ran World of Warcraft on maximum settings, and that's all that mattered to teenage me.</p>
<h2 id="heading-the-brother-borrowing-incident">The Brother "Borrowing" Incident</h2>
<p>When I moved out west, my computer stayed behind. At some point, my brother decided my PC looked lonely and "borrowed" it without asking (as brothers do). Then Christmas rolled around, and guess what appeared under the tree with my name on it? MY OWN PC. With a blown power supply.</p>
<p>I never did replace that power supply. The PC sat gathering dust, a monument to sibling shenanigans and the importance of surge protectors.</p>
<h2 id="heading-the-corporate-castoff-renaissance">The Corporate Castoff Renaissance</h2>
<p>Years later, that same brother (yes, redemption arc incoming) was working at a bank that was throwing out perfectly good Dell Optiplex computers. Their only crime? Slow hard drives. These machines were destined for a landfill until he rescued one and brought one to me.</p>
<p>The transformation was mind-blowing: swapping the old HDD for an SSD made boot time instantaneous. We're talking from "go make a sandwich while Windows loads" to "blink and you'll miss it." These corporate castoffs were treasure in disguise!</p>
<p>I happily used one of these rescued machines for years, only upgrading the video card with something cheap off eBay and naturally the power supply to support it. The best part? Unlike many modern proprietary systems, these old Dells accepted standard parts without too much fuss.</p>
<h2 id="heading-modern-times-from-prebuilt-to-power-user">Modern Times: From Prebuilt to Power User</h2>
<p>Fast forward to last year. I did something that would make younger me gasp in horror—I bought a prebuilt PC from Walmart. I know, I know! But with component prices being what they were, it was actually a good deal.</p>
<p>Of course, I couldn't leave well enough alone. Soon I was upgrading to a Ryzen 5900X and stuffing it with 4x32GB RAM chips because... reasons? Definitely overkill since I don't even game much anymore. But there's something deeply satisfying about having more RAM than you'll ever need.</p>
<p>When my fiancée moved in, I wanted us to have matching battle stations so we could work/game together. My brilliant plan: use the old Dell Optiplex case as the foundation for a second build.</p>
<p>Cue the comedy of errors:</p>
<ol>
<li><p>Bought a high-end Radeon 7600XT, only to discover neither of my power supplies could support it.</p>
</li>
<li><p>Tried to install the motherboard in the Optiplex case, only to find out it does not support it.</p>
</li>
<li><p>Attempted to bench test with my spare PSU, which didn't have the required 8-pin connector.</p>
</li>
<li><p>Dismantled my prebuilt setup to test the new motherboard.</p>
</li>
<li><p>Got the dreaded amber DRAM light.</p>
</li>
<li><p>Finally realized I needed to actually connect a GPU to see anything (rookie mistake).</p>
</li>
<li><p>Swapped in the 5900X and... red CPU light. Heart attack moment.</p>
</li>
<li><p>Discovered several bent pins on my precious processor.</p>
</li>
</ol>
<p>Ever tried to straighten CPU pins? It's like performing microsurgery with household items. I used a playing card to view each row and a sewing needle for gentle bending. After a significant amount of stress and perhaps some foul language, I managed to somehow, SOMEHOW, fix it without breaking off a single pin. Special thanks to Jayanti for holding the flashlight because I could not see a thing without her.</p>
<h2 id="heading-the-unfinished-symphony">The Unfinished Symphony</h2>
<p>The second PC isn't completed yet, but it's coming. It'll be a fully custom AM4 build featuring some of the best components it will support. Probably not the wisest financial decision, but since when has PC building been about making sensible choices?</p>
<p>Looking back at my journey from that hand-me-down 286 to performing pin surgery on high-end AMD processors, I can't help but marvel at how far both technology and my relationship with it have come. From accidentally formatting drives with <code>del *.*</code> to carefully repairing processors that cost more than my first three computers combined, each disaster and triumph has deepened my understanding of these machines.</p>
<p>My dad's tech obsession became my own, passing down through the generations like some kind of digital inheritance. That rural West Virginia kid with a 286 eventually became someone who could resurrect dead machines and build new ones from scratch. (On a good day)</p>
<p>Would I recommend this hobby to others? Absolutely! Just maybe don't start by accidentally hosting web servers, formatting your boot drive, or lending your PC to siblings.  </p>
<p>Thank you for joining me on this nostalgic tech journey! Did you experience the hand-me-down computer cycle, DOOM LAN parties, or GeoCities web design? Share your first computer or gaming memories in the comments—I would love to hear how your path through personal computing compares to mine.</p>
]]></content:encoded></item><item><title><![CDATA[The Future of Web Components]]></title><description><![CDATA[Explore the evolving landscape of Web Components, their role in modern web development, and emerging trends set to shape the future web ecosystem]]></description><link>https://journeytocode.hashnode.dev/the-future-of-web-components</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/the-future-of-web-components</guid><category><![CDATA[imperative slotting]]></category><category><![CDATA[Future]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[w3c]]></category><category><![CDATA[ShadowDOM]]></category><category><![CDATA[CSS]]></category><category><![CDATA[styling]]></category><category><![CDATA[State Management ]]></category><category><![CDATA[context API]]></category><category><![CDATA[declarative-shadow-dom]]></category><category><![CDATA[Pseudo-classes ]]></category><category><![CDATA[WebAssembly]]></category><category><![CDATA[wasm]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 20 Apr 2025 04:00:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744748334425/c2904215-7eff-4555-949a-c580da30241e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Web Components have come a long way since their introduction, evolving from a niche standard to a cornerstone of modern web development. As frameworks rise and fall in popularity, the framework-agnostic design of Web Components positions them as a durable solution for building reusable, interoperable UI elements that stand the test of time.</p>
<p>In this final article of our Web Components series, we'll explore beyond the present—examining emerging trends, community innovations, and the exciting possibilities that lie on the horizon. The future of Web Components isn't just about technical specifications; it's about how they'll reshape our collective approach to building for the web.</p>
<h2 id="heading-retrospective">Retrospective</h2>
<h3 id="heading-style-encapsulation-the-shadow-doms-protective-barrier">Style Encapsulation: The Shadow DOM’s Protective Barrier</h3>
<p>The true power of Web Components lies in their ability to maintain style isolation through Shadow DOM. Unlike traditional HTML elements that are vulnerable to style collisions in complex applications, components with encapsulated styles create a protective boundary that shields their internal structure from external CSS. This isolation enables developers to write simpler, more intuitive CSS without worrying about specificity wars or unintended side effects. A button component, for instance, can maintain its carefully crafted appearance regardless of whether it's placed inside a legacy application with aggressive global styles or a modern design system with strict visual guidelines. This encapsulation doesn't just prevent styles from leaking in—it also prevents internal styles from contaminating the rest of the page, creating a clean contract between the component and its containing application. As applications grow in complexity and teams scale, this level of predictability becomes increasingly valuable, allowing components to be developed, tested, and deployed with confidence.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/yyyBdJo">https://codepen.io/mnichols08/pen/yyyBdJo</a></div>
<p> </p>
<h3 id="heading-fluid-theming-with-css-custom-properties">Fluid Theming with CSS Custom Properties</h3>
<p>CSS Custom Properties, also known as CSS Variables, have revolutionized how we approach theming across component boundaries. Unlike earlier approaches that struggled with Shadow DOM's encapsulation, custom properties flow naturally through component boundaries, creating an elegant bridge between isolation and customization. By defining a consistent set of design tokens at the root level—like <code>--primary-color</code>, <code>--font-family</code>, or <code>--border-radius</code>—developers can establish a coherent design language that penetrates even the most deeply nested components. This creates a powerful contract between component authors and consumers: components can expose "styling hooks" through properties while maintaining their internal structure, while applications can inject theme values without needing to understand component internals. The real magic emerges when these properties are combined with dynamic JavaScript manipulation, enabling real-time theme switching, dark mode toggles, or even user-customized themes without complex style recalculations. For large-scale applications and design systems built with Web Components, this approach has become the foundation of scalable theming strategies, balancing component autonomy with system-wide visual consistency.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/raaBgdy?editors=1111">https://codepen.io/mnichols08/pen/raaBgdy?editors=1111</a></div>
<p> </p>
<h3 id="heading-cross-component-styling-how-css-part-and-the-future-of-component-styling">Cross-Component Styling: How CSS ::part() and the Future of Component Styling</h3>
<p>The CSS ::part() pseudo-element represents a significant breakthrough in Web Component styling flexibility. Unlike earlier approaches that compromised encapsulation, ::part() allows component authors to selectively expose specific elements within their shadow DOM for external styling. As MDN documentation explains, it works by targeting "any element within a shadow tree that has a matching part attribute". Component authors maintain control over which elements can be styled while still providing customization points for developers using their components. This creates a clean "styling API" that preserves encapsulation while enabling theming across component boundaries. The syntax is straightforward – simply add a <code>part</code> attribute to internal elements you want to expose, then use the <code>::part()</code> selector from outside to target them. For component systems and design libraries, this feature is transformative, enabling consistent theming across nested components without sacrificing the isolation that makes Web Components so robust.</p>
<p>This feature shows the Web Component ecosystem maturing to address real-world styling needs, finding the balance between encapsulation and customization that earlier approaches like deep selectors or custom properties couldn't fully achieve.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/dPPbayV">https://codepen.io/mnichols08/pen/dPPbayV</a></div>
<p> </p>
<h3 id="heading-custom-events-the-language-of-component-conversation">Custom Events: The Language of Component Conversation</h3>
<p>Component communication represents one of Web Components' most elegant solutions, striking a balance between isolation and interconnection. At its core lies the Custom Events API, which enables components to dispatch and listen for events that cross shadow boundaries without sacrificing encapsulation. This pattern follows the platform's own event model, making components feel like natural HTML citizens while maintaining loose coupling. For more complex scenarios, patterns have emerged that mirror popular framework approaches—from simple event buses that facilitate application-wide messaging to sophisticated store implementations that provide observable state changes. The true strength of these approaches is their framework agnosticism; a component built with Lit can seamlessly communicate with one built in vanilla JavaScript, breaking down the walled gardens that have fragmented frontend development. As applications scale, this communication layer becomes the invisible glue that transforms isolated components into cohesive, interactive systems.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/qEEWzRL">https://codepen.io/mnichols08/pen/qEEWzRL</a></div>
<p> </p>
<h2 id="heading-the-rising-tide-emerging-trends">The Rising Tide: Emerging Trends</h2>
<p>As technology evolves, new paradigms reshape how we build digital experiences. Among these shifts, the push toward modular, interoperable solutions has gained undeniable momentum—nowhere more visibly than in frontend development.</p>
<h3 id="heading-web-component-libraries-on-the-rise">Web Component Libraries on the Rise</h3>
<p>Web component libraries have surged in popularity as essential tools for building fast, scalable, and maintainable user interfaces, driven by their framework-agnostic nature, native browser compatibility, and growing standardization. Among the most prominent libraries is <strong>Lit</strong> (developed by Google), celebrated for its simplicity, lightweight footprint, and high performance. Lit leverages modern Web Components standards like custom elements and shadow DOM, combined with its reactive templating system, to enable developers to create reusable components efficiently. Its declarative syntax and seamless integration with JavaScript, HTML, and CSS make it a top choice for teams prioritizing speed and flexibility.</p>
<p><strong>Shoelace</strong> stands out as a UI library offering a rich suite of pre-built, customizable components that work across frameworks. With a focus on design consistency and accessibility, Shoelace emphasizes declarative styling and zero dependencies, allowing developers to adopt components "out of the box" while retaining full control over themes and behavior via CSS custom properties. Its use of native Web Components ensures broad compatibility and future-proofing.</p>
<p><strong>FAST</strong> (by Microsoft) is a modern library prioritizing performance, developer ergonomics, and adaptive interfaces. Built on Web Components, FAST provides tools like the FAST Element base class and a design system for creating lightweight, high-performance UIs. Its template engine optimizes rendering speed, while its modular architecture supports incremental adoption in existing projects.</p>
<p><strong>Stencil</strong>, a compiler rather than a traditional library, bridges the gap between JSX/React-like syntax and Web Components. It allows developers to write components with TypeScript and JSX, then compiles them into standards-based custom elements. Stencil’s focus on lazy-loading, code splitting, and tree-shaking ensures optimal runtime performance, making it ideal for large-scale applications.</p>
<p>These libraries empower developers to build reusable, cross-platform components with minimal boilerplate, while offering features like scoped CSS (via shadow DOM), server-side rendering support, and seamless integration with modern build tools. Their emphasis on browser standards reduces vendor lock-in and ensures longevity. However, challenges include varying learning curves, the need for polyfills in legacy browsers (e.g., IE11), and potential SEO considerations due to client-side rendering. Additionally, shadow DOM encapsulation can complicate global styling strategies.</p>
<p>As browser support for Web Components matures, libraries like Lit, Shoelace, FAST, and Stencil are increasingly vital for teams seeking performant, maintainable solutions. They not only streamline development workflows but also align with the industry’s shift toward modular, interoperable architectures, solidifying their role in the future of web development.</p>
<h3 id="heading-is-state-management-going-native-beyond-properties-and-attributes">Is State Management Going Native?: Beyond Properties and Attributes</h3>
<p>State management within Web Components has evolved from simple property getters and setters to sophisticated reactive systems that rival established framework solutions. Unlike traditional elements that update through direct DOM manipulation, modern Web Components employ observability patterns that elegantly synchronize internal state with rendered output. The proposed Custom State Pseudo-Class specification takes this further, exposing component states like "loading" or "expanded" directly to CSS, enabling powerful styling based on component behavior without breaking encapsulation. Libraries like Lit State and Web Signals have emerged to facilitate cross-component communication, allowing state to flow between related components without tight coupling. The true innovation comes in how these approaches maintain the platform-native spirit of Web Components—avoiding framework lock-in while still providing the reactivity developers expect. As applications grow increasingly complex, component authors are adopting patterns like stores, reducers, and context-like providers that feel familiar to framework developers but remain firmly rooted in standards-based web platform capabilities. This balance of sophistication and standards-compliance represents the maturing ecosystem around Web Components, addressing what was once considered their primary weakness compared to framework-based solutions.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/RNNbmOd">https://codepen.io/mnichols08/pen/RNNbmOd</a></div>
<p> </p>
<h3 id="heading-what-is-a-web-component-library">What is a Web Component Library?</h3>
<p>Advanced state management approaches have brought sophisticated patterns like Context to the world of Web Components, elevating them beyond isolated elements to cohesive systems. Inspired by React's Context API but built on platform standards, these implementations create hierarchical state containers that elegantly solve the theme propagation challenge. A ThemeProvider component, for instance, can establish a subscription model where child components automatically receive and react to theme changes without direct parent-child coupling. This pattern creates a powerful alternative to CSS Custom Properties for complex scenarios, enabling not just style values but entire theme configurations to flow through component trees. Libraries like Lit State have formalized these approaches, offering lightweight stores with observability that feels familiar to Redux or Zedustand developers while maintaining Web Components' framework-independence. The true elegance emerges in how these systems leverage existing DOM relationships rather than imposing artificial hierarchies—context flows naturally through component composition, just as it does in native HTML. For enterprise applications with complex theming requirements, this approach transforms Web Components from isolated islands to orchestrated ecosystems without compromising their fundamental platform alignment.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/JooPzqg">https://codepen.io/mnichols08/pen/JooPzqg</a></div>
<p> </p>
<h3 id="heading-declarative-shadow-dom-server-side-renaissance">Declarative Shadow DOM: Server-Side Renaissance</h3>
<p>One of the most significant limitations of Web Components has long been server-side rendering capabilities. The Declarative Shadow DOM (DSD) is changing this landscape, enabling Web Components to be rendered on the server before reaching the browser.</p>
<p>Frameworks like Astro and Enhance.dev are embracing this approach, delivering Web Components with the performance benefits of server-side rendering:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">my-card</span>&gt;</span>  
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">shadowroot</span>=<span class="hljs-string">"open"</span>&gt;</span>  
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css"><span class="hljs-selector-pseudo">:host</span> { <span class="hljs-attribute">display</span>: block; }</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>  
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"title"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>  
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>  
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"title"</span>&gt;</span>Hello from the server!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>  
<span class="hljs-tag">&lt;/<span class="hljs-name">my-card</span>&gt;</span>
</code></pre>
<p>This pattern is already improving initial load times in production applications. As we move into 2025, expect DSD adoption to accelerate, particularly with edge runtime integrations like Cloudflare Workers making server-rendered components even more accessible.</p>
<p>Check out this interactive demo on CodePen to see this pattern in action:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/dPPbBoa">https://codepen.io/mnichols08/pen/dPPbBoa</a></div>
<p> </p>
<h3 id="heading-state-management-evolves">State Management Evolves</h3>
<p>State management in complex Web Components can quickly become unwieldy. The community has responded with elegant solutions that feel increasingly native to the platform.</p>
<p>Projects like Web Signals offer a React-inspired state model without the framework dependencies, while Lit State provides reactive state management specifically tailored to component systems. Most exciting is the proposed Custom State Pseudo-Class that will allow styling components based on their internal state:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">my-element</span><span class="hljs-selector-pseudo">:--state(loading)</span> {  
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.5</span>;  
}
</code></pre>
<p>These approaches promise to be transformative for complex component systems, addressing one of the key challenges in Web Component adoption.</p>
<p>Here is another CodePen that demonstrates how we might be able to use the CSS ::—state() pseudo class in the future.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/vEEBbXr">https://codepen.io/mnichols08/pen/vEEBbXr</a></div>
<p> </p>
<h3 id="heading-micro-frontends-the-enterprise-adoption">Micro-Frontends: The Enterprise Adoption</h3>
<p>Large enterprises are increasingly embracing Web Components as the foundation of their micro-frontend strategies. Companies that once viewed them as too experimental are now seeing their potential for cross-framework compatibility.</p>
<p>Organizations like Adobe (with Spectrum Web Components) and Microsoft (with Fast Design) are building entire design systems using Web Components that work seamlessly across React, Angular, and Vue applications. Tools like SingleSPA and Webpack Module Federation now offer first-class Web Component support for these multi-framework architectures.</p>
<p>The pattern is clear: as organizations struggle with framework fragmentation, Web Components offer a unifying language that works across team boundaries and technology stacks.</p>
<h2 id="heading-community-momentum-tools-amp-libraries">Community Momentum: Tools &amp; Libraries</h2>
<h3 id="heading-lit-40-the-evolution-continues">Lit 4.0: The Evolution Continues</h3>
<p>The Lit library continues to lead innovation in the Web Component ecosystem. The upcoming Lit 4.0 brings compiled template literals (delivering 20% faster renders) and enhanced reactive controllers for shared logic patterns.</p>
<p>Their new <code>@lit-labs/analyzer</code> CLI automatically generates TypeScript types and documentation from component code. These quality-of-life improvements address the pain points that have historically made Web Components feel cumbersome compared to framework alternatives.</p>
<h3 id="heading-scoped-custom-element-registries-solving-collisions">Scoped Custom Element Registries: Solving Collisions</h3>
<p>A persistent challenge in large applications has been the global nature of custom element registration. The proposed Scoped Registries specification offers an elegant solution:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> registry = <span class="hljs-keyword">new</span> CustomElementRegistry();  
registry.define(<span class="hljs-string">'my-element'</span>, MyElement);  
<span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'my-element'</span>, { registry });
</code></pre>
<p>Libraries like <code>@webcomponents/scoped-registry-polyfill</code> are already experimenting with this pattern, allowing different parts of an application to use components with the same names without conflicts. This represents a critical evolution for enterprise adoption, especially in large-scale applications.</p>
<p>Below is a CodePen that demonstrates the usefulness of having a scoped custom registry for a component.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/raaBogO">https://codepen.io/mnichols08/pen/raaBogO</a></div>
<p> </p>
<h3 id="heading-testing-gets-easier">Testing Gets Easier</h3>
<p>Testing has long been the Achilles' heel of Web Component development—but the landscape is finally changing. Web Test Runner brings native Web Component support to testing workflows, while Storybook 8.0 now auto-generates documentation and interactive demos for custom elements.</p>
<p>These tools are closing the development experience gap that has kept many developers tethered to framework-specific workflows.</p>
<h2 id="heading-on-the-horizon-speculative-features">On the Horizon: Speculative Features</h2>
<h3 id="heading-imperative-slotting-api">Imperative Slotting API</h3>
<p>The proposed Imperative Slot API represents perhaps the most significant enhancement to the core Web Components model since Shadow DOM itself. This API will allow dynamic control over slot content via JavaScript:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> slot = element.attachSlot(<span class="hljs-string">'header'</span>);  
slot.appendChild(<span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'h1'</span>));
</code></pre>
<p>For performance-critical applications that need to bypass DOM operations, this approach could unlock significant optimizations and simplify complex component composition patterns.</p>
<h3 id="heading-cross-component-styles">Cross-Component Styles</h3>
<p>One of the challenges with Shadow DOM has been the balance between style isolation and style sharing. The CSS Scope Proposal introduces an elegant middle path with the <code>@scope</code> directive:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@scope</span> (.card) {  
  <span class="hljs-selector-tag">h1</span> { <span class="hljs-attribute">color</span>: red; } <span class="hljs-comment">/* Only applies inside .card */</span>  
}
</code></pre>
<p>This would provide the best of both worlds: style isolation where needed and controlled style inheritance where beneficial.</p>
<h3 id="heading-webassembly-integration">WebAssembly Integration</h3>
<p>The most speculative—but potentially most transformative—frontier is WebAssembly integration. Projects like wasm-bindgen are already exploring paths for Rust-based custom elements that could deliver near-native performance.</p>
<p>Web Components that process complex data visualizations or run machine learning models with the efficiency of compiled code, while maintaining a declarative interface, would open incredible new possibilities.</p>
<p>Check out a simulation of how we can bring WebAssembly into a web component below:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/dPPbrbw">https://codepen.io/mnichols08/pen/dPPbrbw</a></div>
<p> </p>
<h2 id="heading-challenges-we-must-overcome">Challenges We Must Overcome</h2>
<h3 id="heading-framework-interoperability">Framework Interoperability</h3>
<p>While React 19 and Angular 18 have improved Web Component compatibility, framework-specific patterns (particularly React's hooks system) still create friction with vanilla custom elements. The community is addressing this through the Custom Element Manifest standard, which aims to provide consistent metadata for better tooling and framework integration.</p>
<h3 id="heading-accessibility-gaps">Accessibility Gaps</h3>
<p>Despite progress, many Web Components still lack built-in accessibility features. The Accessibility Object Model (AOM) proposal offers a promising solution, allowing components to programmatically declare their semantic roles:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyButton</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{  
  <span class="hljs-keyword">constructor</span>() {  
    <span class="hljs-built_in">super</span>();  
    <span class="hljs-built_in">this</span>.accessibleRole = <span class="hljs-string">'button'</span>; <span class="hljs-comment">// Proposed API  </span>
  }  
}
</code></pre>
<p>This evolution is particularly important for components used in applications with strict accessibility requirements, such as government and education sectors.</p>
<h3 id="heading-education-amp-advocacy">Education &amp; Advocacy</h3>
<p>Technical challenges aside, the most persistent barrier to Web Components adoption remains education. Misconceptions about complexity and boilerplate requirements continue to deter developers from exploring the standard.</p>
<p>Initiatives like webcomponents.org and OpenWC are making strides in curating best practices, while platforms like StackBlitz now offer zero-configuration Web Component development environments. These efforts to lower the barrier to entry will be crucial for the next wave of adoption.</p>
<h2 id="heading-the-ecosystem-in-2026-and-beyond">The Ecosystem in 2026 and Beyond</h2>
<p>Looking further ahead, several transformative shifts appear on the horizon:</p>
<ul>
<li><p><strong>Browser vendors</strong> are experimenting with native lazy-loading for custom elements and GPU-accelerated shadow roots, promising significant performance improvements.</p>
</li>
<li><p><strong>Design tools</strong> like Webflow and Figma are moving toward generating Web Components directly from designs, potentially revolutionizing the designer-developer workflow.</p>
</li>
<li><p><strong>Decentralized applications</strong> are increasingly leveraging Web Components for their portability and framework independence, making them ideal building blocks for blockchain-based interfaces.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The future of Web Components is bright and increasingly central to the web platform's evolution. As browsers continue to implement native solutions for common patterns, the line between "framework features" and "platform features" will increasingly blur, with Web Components sitting at the intersection.</p>
<p>To stay at the forefront of this evolution:</p>
<ul>
<li><p>Follow the <a target="_blank" href="https://www.w3.org/community/webcomponents/">W3C Web Components Community Group</a> for standards updates</p>
</li>
<li><p>Experiment with cutting-edge tools like Lit Labs and Stencil</p>
</li>
<li><p>Share your work and learn from others</p>
</li>
</ul>
<p>Thank you for joining this exploration of Web Components throughout this article series. Whether you're building a small blog or the next Netflix, these technologies can enhance your development experience and create more resilient, interoperable user interfaces.</p>
]]></content:encoded></item><item><title><![CDATA[Advanced Patterns & Integration with Frameworks]]></title><description><![CDATA[Connect Web Components with React, Angular, Vue using advanced integration patterns to optimize performance and enhance developer experience]]></description><link>https://journeytocode.hashnode.dev/advanced-patterns-and-integration-with-frameworks</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/advanced-patterns-and-integration-with-frameworks</guid><category><![CDATA[Web Components]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[React]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 13 Apr 2025 15:21:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744557657681/d9582610-4b08-4bd6-9307-bfffd36749ce.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Web Components promise the holy grail of front-end development: truly reusable, framework-agnostic components that work anywhere. Yet many developers struggle to integrate these universal building blocks with popular frameworks like React, Angular, or Vue. In this guide, we'll explore advanced patterns that bridge these worlds, optimize performance, and ensure your Web Components shine in any ecosystem.</p>
<h2 id="heading-why-framework-integration-matters">Why Framework Integration Matters</h2>
<p>Web Components were designed to be universal, but the reality of modern web development means they must coexist with frameworks that have their own component models and lifecycles. Successful integration requires understanding both worlds and the boundaries between them.</p>
<blockquote>
<p>"The true power of Web Components isn't isolation from frameworks, but seamless cooperation with them."</p>
</blockquote>
<p>What makes integration challenging?</p>
<ul>
<li><p><strong>Different Data Flow Models</strong>: React's unidirectional data flow differs fundamentally from the property-based approach of Web Components.</p>
</li>
<li><p><strong>Event Handling Discrepancies</strong>: Each framework has its own event system that must be reconciled with the DOM event model.</p>
</li>
<li><p><strong>Lifecycle Management</strong>: Coordinating component lifecycle events between frameworks and Web Components requires careful orchestration.</p>
</li>
</ul>
<p>Let's explore how to overcome these challenges with practical, reusable patterns.</p>
<h2 id="heading-seamless-integration-with-modern-frameworks">Seamless Integration with Modern Frameworks</h2>
<h3 id="heading-react-integration-strategies">React Integration Strategies</h3>
<p>React's declarative approach to UI challenges direct integration with the imperative DOM APIs of Web Components. Here are advanced patterns to bridge this gap:</p>
<h4 id="heading-1-the-ref-pattern-direct-dom-access">1. The Ref Pattern: Direct DOM Access</h4>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyReactComponent</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> wcRef = useRef(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (wcRef.current) {
      <span class="hljs-comment">// Direct property assignment for complex data</span>
      wcRef.current.complexData = { <span class="hljs-attr">key</span>: <span class="hljs-string">"value"</span> };

      <span class="hljs-comment">// Event handling with proper cleanup</span>
      <span class="hljs-keyword">const</span> handleEvent = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(e.detail);
      wcRef.current.addEventListener(<span class="hljs-string">'custom-event'</span>, handleEvent);

      <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
        wcRef.current.removeEventListener(<span class="hljs-string">'custom-event'</span>, handleEvent);
      };
    }
  }, []);

  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">my-web-component</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{wcRef}</span> <span class="hljs-attr">string-prop</span>=<span class="hljs-string">"This works"</span> /&gt;</span></span>;
}
</code></pre>
<p><strong>Key Insights:</strong></p>
<ul>
<li><p>React passes string attributes directly, but complex data requires refs</p>
</li>
<li><p>Event handling needs manual listeners with proper cleanup</p>
</li>
<li><p>Ref access enables imperative methods on Web Components</p>
</li>
</ul>
<h4 id="heading-2-the-wrapper-pattern-creating-react-friendly-components">2. The Wrapper Pattern: Creating React-Friendly Components</h4>
<p>React developers expect components with React-like patterns. Create wrapper components that abstract Web Component peculiarities:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// Wrapper that handles property mapping and events</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">EnhancedWebComponent</span>(<span class="hljs-params">{ data, onCustomEvent, children }</span>) </span>{
  <span class="hljs-keyword">const</span> ref = useRef(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> element = ref.current;
    <span class="hljs-keyword">if</span> (element) {
      <span class="hljs-comment">// Set complex data</span>
      element.data = data;

      <span class="hljs-comment">// Handle events</span>
      <span class="hljs-keyword">const</span> eventHandler = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> onCustomEvent(e.detail);
      element.addEventListener(<span class="hljs-string">'custom-event'</span>, eventHandler);

      <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> element.removeEventListener(<span class="hljs-string">'custom-event'</span>, eventHandler);
    }
  }, [data, onCustomEvent]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">my-web-component</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{ref}</span>&gt;</span>
      {children}
    <span class="hljs-tag">&lt;/<span class="hljs-name">my-web-component</span>&gt;</span></span>
  );
}

<span class="hljs-comment">// Usage feels like a normal React component</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">EnhancedWebComponent</span> 
      <span class="hljs-attr">data</span>=<span class="hljs-string">{{complex:</span> "<span class="hljs-attr">data</span>"}} 
      <span class="hljs-attr">onCustomEvent</span>=<span class="hljs-string">{handleEvent}</span>
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Child content<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">EnhancedWebComponent</span>&gt;</span></span>
  );
}
</code></pre>
<p><strong>Interactive Demo:</strong> Try the React integration patterns yourself</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/qEBeQgd">https://codepen.io/mnichols08/pen/qEBeQgd</a></div>
<p> </p>
<h3 id="heading-angular-integration-strategies">Angular Integration Strategies</h3>
<p>Angular has built-in support for Web Components through its <code>@angular/elements</code> package, but deeper integration still requires specific techniques:</p>
<h4 id="heading-1-element-binding-with-custom-directives">1. Element Binding with Custom Directives</h4>
<pre><code class="lang-typescript"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'my-web-component'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> WebComponentDirective <span class="hljs-keyword">implements</span> OnInit, OnChanges, OnDestroy {
  <span class="hljs-meta">@Input</span>() complexData: <span class="hljs-built_in">any</span>;
  <span class="hljs-meta">@Output</span>() customEvent = <span class="hljs-keyword">new</span> EventEmitter&lt;<span class="hljs-built_in">any</span>&gt;();

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> el: ElementRef</span>) {}

  ngOnInit() {
    <span class="hljs-built_in">this</span>.el.nativeElement.addEventListener(<span class="hljs-string">'custom-event'</span>, 
      <span class="hljs-built_in">this</span>.handleCustomEvent.bind(<span class="hljs-built_in">this</span>));
  }

  ngOnChanges(changes: SimpleChanges) {
    <span class="hljs-keyword">if</span> (changes.complexData) {
      <span class="hljs-built_in">this</span>.el.nativeElement.complexData = <span class="hljs-built_in">this</span>.complexData;
    }
  }

  ngOnDestroy() {
    <span class="hljs-built_in">this</span>.el.nativeElement.removeEventListener(<span class="hljs-string">'custom-event'</span>, 
      <span class="hljs-built_in">this</span>.handleCustomEvent.bind(<span class="hljs-built_in">this</span>));
  }

  <span class="hljs-keyword">private</span> handleCustomEvent(event: CustomEvent) {
    <span class="hljs-built_in">this</span>.customEvent.emit(event.detail);
  }
}
</code></pre>
<p><strong>Key Insights:</strong></p>
<ul>
<li><p>Angular directives provide clean property and event binding</p>
</li>
<li><p>OnChanges lifecycle hook updates properties efficiently</p>
</li>
<li><p>ElementRef gives direct access to the native element</p>
</li>
</ul>
<p><strong>Interactive Demo:</strong> Explore Angular integrations</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/XJWvobr">https://codepen.io/mnichols08/pen/XJWvobr</a></div>
<p> </p>
<h3 id="heading-vue-integration-strategies">Vue Integration Strategies</h3>
<p>Vue offers excellent built-in support for Web Components with its attribute and event binding syntax:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">my-web-component</span> 
    <span class="hljs-attr">:complex-prop</span>=<span class="hljs-string">"myData"</span> 
    @<span class="hljs-attr">custom-event</span>=<span class="hljs-string">"handleEvent"</span>
  /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  data() {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">myData</span>: { <span class="hljs-attr">key</span>: <span class="hljs-string">'value'</span> }
    }
  },
  <span class="hljs-attr">methods</span>: {
    handleEvent(event) {
      <span class="hljs-built_in">console</span>.log(event.detail);
    }
  },
  mounted() {
    <span class="hljs-comment">// For cases where direct property access is needed</span>
    <span class="hljs-built_in">this</span>.$refs.myComponent.directMethod();
  }
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p><strong>Key Insights:</strong></p>
<ul>
<li><p>Vue's <code>:prop</code> syntax works with kebab-case attributes</p>
</li>
<li><p>Native event handling with <code>@event-name</code></p>
</li>
<li><p>Vue's reactivity system naturally updates Web Component properties</p>
</li>
</ul>
<p><strong>Interactive Demo:</strong> Explore Vue integrations</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/YPzmRRW">https://codepen.io/mnichols08/pen/YPzmRRW</a></div>
<p> </p>
<h3 id="heading-svelte-integration-strategies">Svelte Integration Strategies</h3>
<p>Svelte treats Web Components as first-class citizens and makes integration particularly straightforward:</p>
<pre><code class="lang-svelte"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span></span><span class="javascript">
  <span class="hljs-keyword">import</span> { onMount } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte'</span>;

  <span class="hljs-keyword">let</span> el;
  <span class="hljs-keyword">let</span> myData = { <span class="hljs-attr">key</span>: <span class="hljs-string">'value'</span> };

  <span class="hljs-comment">// Reactive assignments automatically update the Web Component</span>
</span><span class="hljs-keyword">  $: </span><span class="javascript"><span class="hljs-keyword">if</span> (el) {
    el.complexData = myData;
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleEvent</span>(<span class="hljs-params">event</span>) </span>{
    <span class="hljs-built_in">console</span>.log(event.detail);
  }

  onMount(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// If needed, do direct manipulation here</span>
    el.addEventListener(<span class="hljs-string">'special-event'</span>, specialHandler);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      el.removeEventListener(<span class="hljs-string">'special-event'</span>, specialHandler);
    }
  });
</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">my-web-component</span> 
  <span class="hljs-attr">bind:this</span>=</span></span><span class="javascript">{el}</span><span class="xml"><span class="hljs-tag"> 
  <span class="hljs-attr">on:custom-event</span>=</span></span><span class="javascript">{handleEvent}</span><span class="xml"><span class="hljs-tag">
/&gt;</span></span>
</code></pre>
<p><strong>Key Insights:</strong></p>
<ul>
<li><p><code>bind:this</code> provides direct access to the element</p>
</li>
<li><p>Svelte's reactivity automatically updates properties</p>
</li>
<li><p>Svelte's event handling works natively with Web Component events</p>
</li>
</ul>
<p><strong>Interactive Demo:</strong> Explore Svelte integration Techniques</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/QwWezeL">https://codepen.io/mnichols08/pen/QwWezeL</a></div>
<p> </p>
<p><strong>Framework Comparison Demo:</strong> Compare integration approaches across frameworks</p>
<h2 id="heading-performance-optimization-techniques">Performance Optimization Techniques</h2>
<h3 id="heading-lazy-loading-strategies">Lazy-Loading Strategies</h3>
<p>Loading Web Components only when needed can significantly improve initial page load performance:</p>
<h4 id="heading-dynamic-import-pattern">Dynamic Import Pattern</h4>
<pre><code class="lang-javascript"><span class="hljs-comment">// Only load when needed</span>
<span class="hljs-keyword">const</span> loadComponent = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> { MyComponent } = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./my-component.js'</span>);
  customElements.define(<span class="hljs-string">'my-component'</span>, MyComponent);
}

<span class="hljs-comment">// Call on demand or when component is about to be needed</span>
button.addEventListener(<span class="hljs-string">'click'</span>, loadComponent);
</code></pre>
<h4 id="heading-intersection-observer-pattern">Intersection Observer Pattern</h4>
<pre><code class="lang-javascript"><span class="hljs-comment">// Setup observer to load component when scrolled into view</span>
<span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> IntersectionObserver(<span class="hljs-function">(<span class="hljs-params">entries</span>) =&gt;</span> {
  entries.forEach(<span class="hljs-function"><span class="hljs-params">entry</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (entry.isIntersecting) {
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/lazy-component.js'</span>)
        .then(<span class="hljs-function">() =&gt;</span> {
          <span class="hljs-comment">// Component is now registered</span>
          observer.unobserve(entry.target);
        });
    }
  });
}, { <span class="hljs-attr">rootMargin</span>: <span class="hljs-string">'100px'</span> });

<span class="hljs-comment">// Observe placeholder elements</span>
<span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'.component-placeholder'</span>)
  .forEach(<span class="hljs-function"><span class="hljs-params">el</span> =&gt;</span> observer.observe(el));
</code></pre>
<p><strong>Interactive Demo:</strong> See lazy-loading in action</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/xbxvQvb">https://codepen.io/mnichols08/pen/xbxvQvb</a></div>
<p> </p>
<h3 id="heading-shadow-dom-performance">Shadow DOM Performance</h3>
<p>The Shadow DOM provides encapsulation but comes with performance considerations:</p>
<ol>
<li><p><strong>Containment for Performance</strong></p>
<pre><code class="lang-css"> <span class="hljs-selector-pseudo">:host</span> {
   <span class="hljs-attribute">contain</span>: content; <span class="hljs-comment">/* Limit style recalculation scope */</span>
   <span class="hljs-attribute">container-type</span>: inline-size; <span class="hljs-comment">/* For container queries */</span>
 }
</code></pre>
</li>
<li><p><strong>Minimizing Selectors</strong></p>
<pre><code class="lang-css"> <span class="hljs-comment">/* Avoid overly complex selectors that cross shadow boundaries */</span>
 <span class="hljs-selector-pseudo">:host</span> <span class="hljs-selector-pseudo">::slotted(</span>*) &gt; * { <span class="hljs-comment">/* Expensive */</span> }

 <span class="hljs-comment">/* Better to use direct, simple selectors */</span>
 <span class="hljs-selector-class">.my-item</span> { <span class="hljs-comment">/* More efficient */</span> }
</code></pre>
</li>
<li><p><strong>Slot Change Optimization</strong></p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// Listen only when needed</span>
 <span class="hljs-keyword">constructor</span>() {
   <span class="hljs-built_in">super</span>();
   <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'slot'</span>)
     .addEventListener(<span class="hljs-string">'slotchange'</span>, <span class="hljs-built_in">this</span>._handleSlotChange);
 }

 <span class="hljs-comment">// Debounce frequent updates</span>
 _handleSlotChange = debounce(<span class="hljs-function">() =&gt;</span> {
   <span class="hljs-built_in">this</span>._processSlottedChildren();
 }, <span class="hljs-number">100</span>);
</code></pre>
</li>
</ol>
<h3 id="heading-render-optimization">Render Optimization</h3>
<p>Efficient rendering patterns can dramatically improve performance:</p>
<ol>
<li><p><strong>Batched DOM Updates</strong></p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// Poor: Multiple separate DOM operations</span>
 <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.title'</span>).textContent = title;
 <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.desc'</span>).textContent = description;

 <span class="hljs-comment">// Better: Batch DOM operations with DocumentFragment</span>
 update(data) {
   <span class="hljs-keyword">const</span> fragment = <span class="hljs-built_in">document</span>.createDocumentFragment();
   <span class="hljs-comment">// Build complete update in memory</span>
   <span class="hljs-keyword">const</span> title = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
   title.className = <span class="hljs-string">'title'</span>;
   title.textContent = data.title;
   fragment.appendChild(title);
   <span class="hljs-comment">// etc...</span>

   <span class="hljs-comment">// Single DOM operation</span>
   <span class="hljs-keyword">const</span> container = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.container'</span>);
   container.innerHTML = <span class="hljs-string">''</span>;
   container.appendChild(fragment);
 }
</code></pre>
</li>
<li><p><strong>RequestAnimationFrame for Visual Updates</strong></p>
<pre><code class="lang-javascript"> updateVisuals() {
   <span class="hljs-comment">// Schedule visual updates in animation frame</span>
   requestAnimationFrame(<span class="hljs-function">() =&gt;</span> {
     <span class="hljs-built_in">this</span>.elements.forEach(<span class="hljs-function"><span class="hljs-params">el</span> =&gt;</span> {
       el.style.transform = <span class="hljs-string">`translate(<span class="hljs-subst">${<span class="hljs-built_in">this</span>.x}</span>px, <span class="hljs-subst">${<span class="hljs-built_in">this</span>.y}</span>px)`</span>;
     });
   });
 }
</code></pre>
</li>
</ol>
<p><strong>Interactive Demo:</strong> Compare optimized vs. unoptimized components</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/VYwoqYm">https://codepen.io/mnichols08/pen/VYwoqYm</a></div>
<p> </p>
<h2 id="heading-advanced-patterns-and-best-practices">Advanced Patterns and Best Practices</h2>
<h3 id="heading-state-management-across-boundaries">State Management Across Boundaries</h3>
<p>Managing state between Web Components and frameworks requires thoughtful patterns:</p>
<h4 id="heading-1-the-context-pattern">1. The Context Pattern</h4>
<p>Create a shared context accessible to all components:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// context.js - Framework agnostic state management</span>
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ComponentContext</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>._data = {};
    <span class="hljs-built_in">this</span>._listeners = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
  }

  get(key) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>._data[key];
  }

  set(key, value) {
    <span class="hljs-built_in">this</span>._data[key] = value;
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>._listeners.has(key)) {
      <span class="hljs-built_in">this</span>._listeners.get(key).forEach(<span class="hljs-function"><span class="hljs-params">callback</span> =&gt;</span> callback(value));
    }
  }

  subscribe(key, callback) {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>._listeners.has(key)) {
      <span class="hljs-built_in">this</span>._listeners.set(key, <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>());
    }
    <span class="hljs-built_in">this</span>._listeners.get(key).add(callback);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>._listeners.get(key).delete(callback);
    };
  }
}

<span class="hljs-comment">// Create shared instance</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> context = <span class="hljs-keyword">new</span> ComponentContext();
</code></pre>
<p>Usage in components:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { context } <span class="hljs-keyword">from</span> <span class="hljs-string">'./context.js'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-comment">// Subscribe to state changes</span>
    <span class="hljs-built_in">this</span>._unsubscribe = context.subscribe(<span class="hljs-string">'userData'</span>, 
      <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> <span class="hljs-built_in">this</span>._updateFromState(data));

    <span class="hljs-comment">// Initial state</span>
    <span class="hljs-built_in">this</span>._updateFromState(context.get(<span class="hljs-string">'userData'</span>));
  }

  disconnectedCallback() {
    <span class="hljs-comment">// Clean up subscription</span>
    <span class="hljs-built_in">this</span>._unsubscribe();
  }

  _updateState(data) {
    <span class="hljs-comment">// Update shared state</span>
    context.set(<span class="hljs-string">'userData'</span>, data);
  }
}
</code></pre>
<h4 id="heading-2-the-message-bus-pattern">2. The Message Bus Pattern</h4>
<p>For loosely coupled components, a pub/sub event bus provides flexible communication:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// event-bus.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EventBus</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.listeners = {};
  }

  on(event, callback) {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.listeners[event]) {
      <span class="hljs-built_in">this</span>.listeners[event] = [];
    }
    <span class="hljs-built_in">this</span>.listeners[event].push(callback);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.off(event, callback);
  }

  off(event, callback) {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.listeners[event]) {
      <span class="hljs-built_in">this</span>.listeners[event] = <span class="hljs-built_in">this</span>.listeners[event]
        .filter(<span class="hljs-function"><span class="hljs-params">cb</span> =&gt;</span> cb !== callback);
    }
  }

  emit(event, data) {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.listeners[event]) {
      <span class="hljs-built_in">this</span>.listeners[event].forEach(<span class="hljs-function"><span class="hljs-params">callback</span> =&gt;</span> {
        callback(data);
      });
    }
  }
}

<span class="hljs-comment">// Shared instance</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> eventBus = <span class="hljs-keyword">new</span> EventBus();
</code></pre>
<h3 id="heading-server-side-rendering-with-web-components">Server-Side Rendering with Web Components</h3>
<p>Server-side rendering improves initial load performance. With Declarative Shadow DOM, you can now SSR Web Components:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Server-rendered output --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">my-component</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">shadowroot</span>=<span class="hljs-string">"open"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
      <span class="hljs-comment">/* Shadow DOM styles */</span>
      <span class="hljs-selector-class">.card</span> { 
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">16px</span>;
        <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ddd</span>;
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">slot</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This content is slotted from light DOM<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">my-component</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Polyfill for browsers without Declarative Shadow DOM --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">if</span> (!HTMLTemplateElement.prototype.hasOwnProperty(<span class="hljs-string">'shadowRoot'</span>)) {
    <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'template[shadowroot]'</span>).forEach(<span class="hljs-function"><span class="hljs-params">template</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> mode = template.getAttribute(<span class="hljs-string">'shadowroot'</span>);
      <span class="hljs-keyword">const</span> shadowRoot = template.parentNode.attachShadow({ mode });
      shadowRoot.appendChild(template.content);
      template.remove();
    });
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h3 id="heading-testing-strategies">Testing Strategies</h3>
<p>Testing Web Components requires specialized approaches:</p>
<h4 id="heading-1-component-unit-testing">1. Component Unit Testing</h4>
<pre><code class="lang-javascript"><span class="hljs-comment">// Using web-test-runner and @open-wc/testing</span>
<span class="hljs-keyword">import</span> { html, fixture, expect } <span class="hljs-keyword">from</span> <span class="hljs-string">'@open-wc/testing'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'../src/my-component.js'</span>;

describe(<span class="hljs-string">'MyComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'renders with default values'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> el = <span class="hljs-keyword">await</span> fixture(html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">my-component</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">my-component</span>&gt;</span>`</span>);

    expect(el.shadowRoot.querySelector(<span class="hljs-string">'.title'</span>).textContent)
      .to.equal(<span class="hljs-string">'Default Title'</span>);
  });

  it(<span class="hljs-string">'updates when properties change'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> el = <span class="hljs-keyword">await</span> fixture(html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">my-component</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">my-component</span>&gt;</span>`</span>);

    el.title = <span class="hljs-string">'New Title'</span>;
    <span class="hljs-keyword">await</span> el.updateComplete; <span class="hljs-comment">// For LitElement components</span>

    expect(el.shadowRoot.querySelector(<span class="hljs-string">'.title'</span>).textContent)
      .to.equal(<span class="hljs-string">'New Title'</span>);
  });

  it(<span class="hljs-string">'fires custom events'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> el = <span class="hljs-keyword">await</span> fixture(html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">my-component</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">my-component</span>&gt;</span>`</span>);

    <span class="hljs-comment">// Setup event listener</span>
    <span class="hljs-keyword">let</span> eventFired = <span class="hljs-literal">false</span>;
    el.addEventListener(<span class="hljs-string">'custom-event'</span>, <span class="hljs-function">() =&gt;</span> eventFired = <span class="hljs-literal">true</span>);

    <span class="hljs-comment">// Trigger event</span>
    el.shadowRoot.querySelector(<span class="hljs-string">'button'</span>).click();

    expect(eventFired).to.be.true;
  });
});
</code></pre>
<h4 id="heading-2-integration-testing-with-frameworks">2. Integration Testing with Frameworks</h4>
<pre><code class="lang-javascript"><span class="hljs-comment">// React Testing Library example</span>
<span class="hljs-keyword">import</span> { render, fireEvent, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;
<span class="hljs-keyword">import</span> MyReactComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'../src/MyReactComponent'</span>;

<span class="hljs-comment">// Register Web Component if not done globally</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'../src/web-components/my-element.js'</span>;

test(<span class="hljs-string">'React component interacts with Web Component'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">MyReactComponent</span> <span class="hljs-attr">initialData</span>=<span class="hljs-string">"test"</span> /&gt;</span></span>);

  <span class="hljs-comment">// Interact with the wrapped Web Component</span>
  fireEvent.click(screen.getByRole(<span class="hljs-string">'button'</span>));

  <span class="hljs-comment">// Check the result</span>
  expect(screen.getByText(<span class="hljs-string">'Success'</span>)).toBeInTheDocument();
});
</code></pre>
<h2 id="heading-the-future-of-web-components">The Future of Web Components</h2>
<p>Web Components continue to evolve with exciting new capabilities on the horizon:</p>
<ul>
<li><p><strong>CSS Shadow Parts</strong>: More sophisticated styling across shadow boundaries</p>
</li>
<li><p><strong>Form-associated Custom Elements</strong>: Better integration with native forms</p>
</li>
<li><p><strong>Constructable Stylesheets</strong>: Improved performance for shared styles</p>
</li>
<li><p><strong>Scoped Custom Element Registries</strong>: Avoiding name conflicts</p>
</li>
<li><p><strong>Declarative Custom Elements</strong>: Simplified component definition</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Web Components shine brightest when they seamlessly integrate with existing frameworks and tools. By leveraging the advanced patterns covered in this guide, you can build truly reusable components that work anywhere while maintaining optimal performance and developer experience.</p>
<p>The future of front-end development isn't about choosing between Web Components and frameworks—it's about building bridges between them to create more maintainable, performant applications.</p>
]]></content:encoded></item><item><title><![CDATA[Real-World Examples & Patterns in Web Components]]></title><description><![CDATA[Explore real-world uses and best practices of web components in production at companies like Google, Microsoft, and Adobe, with examples and patterns]]></description><link>https://journeytocode.hashnode.dev/real-world-examples-and-patterns-in-web-components</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/real-world-examples-and-patterns-in-web-components</guid><category><![CDATA[Web Components]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[learning]]></category><category><![CDATA[fundamentals]]></category><category><![CDATA[HTML5]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 06 Apr 2025 04:00:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743362582776/bab8c6d3-b13d-4e2c-b8e9-6d277dac5474.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today's diverse front-end landscape, Web Components stand out as a platform-native solution for creating reusable UI elements. While frameworks come and go, Web Components offer a standardized approach that works across codebases and technologies. But where can we see them in action? Who's using them in production? Let's explore the real-world implementation of Web Components and the patterns that have emerged as best practices.</p>
<p>Let's start with a basic example of a Web Component. This interactive demo shows the fundamental structure and lifecycle methods that make Web Components work. You can see how the component reacts when it's added to the DOM, removed, or when its attributes change:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/LEYgoxO">https://codepen.io/mnichols08/pen/LEYgoxO</a></div>
<p> </p>
<p>Try clicking the buttons to see the lifecycle methods in action. This simple example demonstrates the core concepts of Web Components: custom elements, shadow DOM encapsulation, and lifecycle callbacks.</p>
<h2 id="heading-whos-using-web-components-today">Who's Using Web Components Today?</h2>
<p>Several major tech companies have embraced Web Components for their production applications:</p>
<h3 id="heading-google">Google</h3>
<p>Google was an early champion of Web Components and continues to use them extensively:</p>
<ul>
<li><p><strong>YouTube</strong>: The YouTube web application uses Web Components for its video player interface, comments section, and recommendation panels.</p>
</li>
<li><p><strong>Google Maps</strong>: Many of the interactive elements in Google Maps are built as custom elements.</p>
</li>
<li><p><strong>Google Earth</strong>: The web version leverages Web Components for its UI controls.</p>
</li>
</ul>
<h3 id="heading-microsoft">Microsoft</h3>
<p>Microsoft has significantly invested in Web Components:</p>
<ul>
<li><p><strong>Microsoft's Fluent UI</strong>: Their design system is implemented as Web Components to ensure consistent UIs across products.</p>
</li>
<li><p><strong>Microsoft 365</strong>: Various applications within the suite use custom elements for shared functionality.</p>
</li>
<li><p><strong>VS Code</strong>: Many interface elements are implemented as Web Components.</p>
</li>
</ul>
<h3 id="heading-adobe">Adobe</h3>
<p>Adobe has embraced Web Components for their design systems:</p>
<ul>
<li><p><strong>Adobe Spectrum</strong>: Their design system is available as Web Components through the <a target="_blank" href="https://opensource.adobe.com/spectrum-web-components/">Spectrum Web Components</a> library.</p>
</li>
<li><p><strong>Adobe Experience Cloud</strong>: Uses components from their Spectrum library across products.</p>
</li>
</ul>
<h3 id="heading-other-notable-examples">Other Notable Examples</h3>
<ul>
<li><p><strong>Salesforce</strong>: Their Lightning Web Components framework is built on the Web Components standards.</p>
</li>
<li><p><strong>ING Bank</strong>: Redesigned their web applications using Web Components for consistent UX across platforms.</p>
</li>
<li><p><strong>IBM</strong>: Carbon design system offers Web Components implementations.</p>
</li>
<li><p><strong>GitHub</strong>: Uses Web Components for various UI elements across their platform.</p>
</li>
<li><p><strong>Netflix</strong>: Has incorporated custom elements in parts of their streaming interface.</p>
</li>
</ul>
<h2 id="heading-popular-web-component-libraries-amp-hosting-strategies">Popular Web Component Libraries &amp; Hosting Strategies</h2>
<h3 id="heading-libraries-and-frameworks">Libraries and Frameworks</h3>
<p>Several libraries have emerged to simplify Web Component development:</p>
<ol>
<li><p><strong>Lit</strong>: Google's lightweight library (successor to Polymer) has become the most popular choice for creating Web Components with minimal boilerplate.</p>
</li>
<li><p><strong>Stencil</strong>: Created by the Ionic team, Stencil generates highly optimized Web Components and can output framework-specific wrappers.</p>
</li>
<li><p><strong>FAST</strong>: Microsoft's adaptive UI library produces Web Components that automatically adapt to user preferences.</p>
</li>
<li><p><strong>Shoelace</strong>: A collection of professionally designed, every day UI components built on a framework-agnostic technology.</p>
</li>
<li><p><strong>Enhance</strong>: A framework that uses progressive enhancement to create multi-page applications with Web Components.</p>
</li>
</ol>
<h3 id="heading-hosting-amp-distribution-strategies">Hosting &amp; Distribution Strategies</h3>
<p>Companies distribute their Web Components in several ways:</p>
<ol>
<li><p><strong>NPM Packages</strong>: The most common approach, allowing developers to install components via package managers.</p>
<ul>
<li>Example: <code>npm install @adobe/spectrum-web-components</code></li>
</ul>
</li>
<li><p><strong>CDN Delivery</strong>: Pre-built components hosted on CDNs allow for quick inclusion without build steps.</p>
<ul>
<li>Example: <code>&lt;script type="module" src="https://unpkg.com/@shoelace-style/shoelace@2.0.0/dist/shoelace.js"&gt;&lt;/script&gt;</code></li>
</ul>
</li>
<li><p><strong>Custom Element Registries</strong>: Specialized registries like <a target="_blank" href="https://www.webcomponents.org/">webcomponents.org</a> catalog available components.</p>
</li>
<li><p><strong>Bundled with Applications</strong>: Many applications bundle custom elements as part of their build process.</p>
</li>
<li><p><strong>Micro-frontend Architecture</strong>: Components distributed across multiple teams but united in a single application.</p>
</li>
</ol>
<h2 id="heading-common-patterns-in-production-web-components">Common Patterns in Production Web Components</h2>
<h3 id="heading-composition-pattern">Composition Pattern</h3>
<p>One of the most powerful patterns is component composition:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Container component --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">user-profile</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- Composable child components --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">user-avatar</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"avatar"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">user-avatar</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">user-info</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"info"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">user-info</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">user-stats</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"stats"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">user-stats</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">user-profile</span>&gt;</span>
</code></pre>
<p>This pattern enables teams to build complex UIs from simpler building blocks, a strategy employed by many design systems.</p>
<p>To see the composition pattern in action, here's an interactive demo that demonstrates how slots distribute content in Web Components:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/pvoxmWj">https://codepen.io/mnichols08/pen/pvoxmWj</a></div>
<p> </p>
<p>This card component uses three distinct slots (header, body, and footer) to create a flexible, reusable UI element. Try toggling different parts of the content to see how:</p>
<ol>
<li><p>The component maintains its structure even when slots are empty</p>
</li>
<li><p>Content from the "light DOM" (your HTML) flows into the designated slots</p>
</li>
<li><p>The component's styling remains consistent regardless of the content provided</p>
</li>
<li><p>The theme can change while preserving the composition structure</p>
</li>
<li><p>The code preview updates to show exactly what HTML you would write to achieve the current state</p>
</li>
</ol>
<p>This pattern is especially powerful for design systems, as it allows components to maintain consistent styling and behavior while giving content authors flexibility to provide custom content for different sections.</p>
<h3 id="heading-propertyattribute-pattern">Property/Attribute Pattern</h3>
<p>Attributes for simple values, properties for complex data:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataTable</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">// Simple configuration via attributes</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() { 
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'loading'</span>, <span class="hljs-string">'pagination'</span>]; 
  }

  <span class="hljs-comment">// Complex data via properties</span>
  <span class="hljs-keyword">set</span> <span class="hljs-title">data</span>(<span class="hljs-params">value</span>) {
    <span class="hljs-built_in">this</span>._data = value;
    <span class="hljs-built_in">this</span>._renderTable();
  }
}
</code></pre>
<p>Google's Material Web Components and Adobe's Spectrum Web Components both follow this pattern.</p>
<p>To better understand how attributes and properties work together in Web Components, let's explore this interactive demo that clearly demonstrates their differences:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/YPzJbxv">https://codepen.io/mnichols08/pen/YPzJbxv</a></div>
<p> </p>
<p>This demo shows how attributes are best for simple values that can be represented in HTML markup (strings, booleans), while properties excel at handling complex data like arrays and objects. Try using the controls to manipulate both attributes and properties, and observe how:</p>
<ol>
<li><p>Attribute changes trigger the <code>attributeChangedCallback()</code> and appear in the HTML markup</p>
</li>
<li><p>Property changes don't affect the HTML markup but can handle complex data structures</p>
</li>
<li><p>The component responds to both types of changes in different ways</p>
</li>
</ol>
<p>This pattern is widely used in production Web Components. For example, Google's Material Web Components use attributes for styling options (like <code>variant</code> or <code>dense</code>), while reserving properties for data binding with complex values.</p>
<h3 id="heading-event-based-communication-pattern">Event-Based Communication Pattern</h3>
<p>Components communicate through custom events:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Inside the component</span>
<span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'item-selected'</span>, {
  <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">composed</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Crosses shadow DOM boundary</span>
  <span class="hljs-attr">detail</span>: { <span class="hljs-attr">id</span>: selectedId }
}));

<span class="hljs-comment">// Parent listening for events</span>
<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'my-component'</span>)
  .addEventListener(<span class="hljs-string">'item-selected'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(e.detail.id));
</code></pre>
<p>GitHub's web components utilize this pattern for their interactive elements.</p>
<p>Let's see events in action with this interactive demo. Here, two completely separate web components communicate through custom events without direct references to each other - a fundamental pattern for creating loosely coupled, reusable components:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/NPWOVXK">https://codepen.io/mnichols08/pen/NPWOVXK</a></div>
<p> </p>
<p>In this example:</p>
<ul>
<li><p>The color picker component dispatches a <code>color-changed</code> event when a color is selected</p>
</li>
<li><p>The event includes detailed data in the <code>detail</code> property (color code and name)</p>
</li>
<li><p>The display component listens for this event and updates accordingly</p>
</li>
<li><p>Notice how <code>bubbles: true</code> and <code>composed: true</code> allow the event to cross shadow DOM boundaries</p>
</li>
</ul>
<h3 id="heading-api-consistency-pattern">API Consistency Pattern</h3>
<p>Successful Web Component libraries establish consistent APIs:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Consistent naming and behavior across components</span>
&lt;sl-button variant=<span class="hljs-string">"primary"</span> size=<span class="hljs-string">"large"</span> disabled&gt;&lt;/sl-button&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">sl-dropdown</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"primary"</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"large"</span> <span class="hljs-attr">disabled</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">sl-dropdown</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">sl-checkbox</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"primary"</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"large"</span> <span class="hljs-attr">disabled</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">sl-checkbox</span>&gt;</span></span>
</code></pre>
<p>Microsoft's FAST components and Shoelace both exemplify this pattern with predictable property names.</p>
<h2 id="heading-real-world-implementation-examples">Real-World Implementation Examples</h2>
<h3 id="heading-design-system-components">Design System Components</h3>
<p>Design systems often implement form controls with enhanced functionality:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EnhancedSelect</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        :host {
          display: inline-block;
          position: relative;
          width: 200px;
        }
        .select-container {
          border: 1px solid var(--border-color, #ccc);
          border-radius: 4px;
          padding: 8px;
        }
        /* Additional styles... */
      &lt;/style&gt;
      &lt;div class="select-container"&gt;
        &lt;slot name="selected-option"&gt;&lt;/slot&gt;
        &lt;div class="dropdown"&gt;
          &lt;slot&gt;&lt;/slot&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-comment">// Implementation details...</span>
  }
}

customElements.define(<span class="hljs-string">'enhanced-select'</span>, EnhancedSelect);
</code></pre>
<p>Now let's explore a fully functional implementation of the EnhancedSelect component that demonstrates how design system principles are applied in practice:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/emYPaVB">https://codepen.io/mnichols08/pen/emYPaVB</a></div>
<p> </p>
<p>This enhanced select component showcases several key design system features:</p>
<ol>
<li><p><strong>Theming</strong> - The component responds to design tokens for colors, spacing, and typography</p>
</li>
<li><p><strong>Consistent Visual Language</strong> - The styling follows design system patterns with proper spacing and visual hierarchy</p>
</li>
<li><p><strong>Accessibility</strong> - The component implements ARIA attributes, keyboard navigation, and focus management</p>
</li>
<li><p><strong>Configurability</strong> - Properties like density, size, and shape variants provide flexibility within the design system</p>
</li>
<li><p><strong>Component API</strong> - The interface follows consistent patterns with attributes and properties</p>
</li>
</ol>
<p>Try changing the theme, density, and other properties to see how the component adapts while maintaining design system consistency. This pattern of using CSS custom properties as design tokens is used by libraries like Adobe's Spectrum Web Components and Microsoft's FAST.</p>
<h3 id="heading-content-widgets">Content Widgets</h3>
<p>News sites and content platforms often use web components for interactive widgets:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ContentRecommendation</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() {
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'category'</span>, <span class="hljs-string">'count'</span>];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    <span class="hljs-keyword">if</span> (oldValue !== newValue) {
      <span class="hljs-built_in">this</span>._fetchRecommendations();
    }
  }

  connectedCallback() {
    <span class="hljs-built_in">this</span>._fetchRecommendations();
  }

  <span class="hljs-keyword">async</span> _fetchRecommendations() {
    <span class="hljs-keyword">const</span> category = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'category'</span>) || <span class="hljs-string">'general'</span>;
    <span class="hljs-keyword">const</span> count = <span class="hljs-built_in">parseInt</span>(<span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'count'</span>) || <span class="hljs-string">'3'</span>, <span class="hljs-number">10</span>);

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`/api/recommendations?category=<span class="hljs-subst">${category}</span>&amp;count=<span class="hljs-subst">${count}</span>`</span>);
      <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
      <span class="hljs-built_in">this</span>._renderRecommendations(data);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Failed to fetch recommendations:'</span>, error);
    }
  }

  _renderRecommendations(items) {
    <span class="hljs-comment">// Render logic...</span>
  }
}

customElements.define(<span class="hljs-string">'content-recommendation'</span>, ContentRecommendation);
</code></pre>
<p>This pattern is used by sites like The Guardian and Washington Post for their recommendation modules.</p>
<p>Here's a working example of a content recommendation component that demonstrates the key concepts of content widgets in web components:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/RNwemdd">https://codepen.io/mnichols08/pen/RNwemdd</a></div>
<p> </p>
<p>This demonstration shows how content-focused components:</p>
<ol>
<li><p><strong>Fetch and display dynamic content</strong> - Simulating API calls to retrieve articles based on user selections</p>
</li>
<li><p><strong>Handle loading states</strong> - Using skeleton loaders to improve perceived performance</p>
</li>
<li><p><strong>Manage error conditions</strong> - Gracefully handling failures with clear user feedback</p>
</li>
<li><p><strong>Communicate via events</strong> - Dispatching custom events when articles are loaded or selected</p>
</li>
<li><p><strong>Respond to configuration changes</strong> - Updating content based on category and count selections</p>
</li>
</ol>
<p>This pattern is commonly used by news sites, blogs, and content platforms to create modular, reusable recommendation widgets that can be placed throughout their applications.</p>
<p>This pattern is similar to what you'd find on content-heavy sites like The Guardian or The Washington Post, where web components encapsulate complex content widgets while maintaining clean integration with the surrounding application.</p>
<h2 id="heading-performance-patterns">Performance Patterns</h2>
<h3 id="heading-lazy-loading-components">Lazy-Loading Components</h3>
<p>Many production Web Components implement lazy-loading to improve performance:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Only load the component when needed</span>
<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#show-comments'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-comment">// Dynamically import the component</span>
  <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/comment-section.js'</span>);

  <span class="hljs-comment">// Now create and use it</span>
  <span class="hljs-keyword">const</span> commentSection = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'comment-section'</span>);
  <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#comments-container'</span>).appendChild(commentSection);
});
</code></pre>
<p>YouTube uses this pattern extensively to load UI components only when needed.</p>
<p>To demonstrate the power of lazy loading web components, here's an interactive example that simulates loading resource-intensive UI components only when needed:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/raNqgEG">https://codepen.io/mnichols08/pen/raNqgEG</a></div>
<p> </p>
<p>This demo shows how lazy loading can dramatically improve initial page load performance by:</p>
<ol>
<li><p><strong>Reducing initial page weight</strong> - Only the core 10KB of code loads initially</p>
</li>
<li><p><strong>Loading components on demand</strong> - Each component is loaded only when requested</p>
</li>
<li><p><strong>Providing visual feedback</strong> - The performance meter shows the impact of each loaded component</p>
</li>
<li><p><strong>Using dynamic imports</strong> - Components are loaded asynchronously without blocking the main thread</p>
</li>
</ol>
<p>Try clicking the buttons to load different components and watch how the page weight increases only when new components are added. This pattern is used extensively by YouTube for their video player interface, where heavy components are loaded only when the user interacts with them.</p>
<h3 id="heading-constructable-stylesheets">Constructable Stylesheets</h3>
<p>For components used multiple times on a page, shared stylesheets improve performance:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Create once, use many times</span>
<span class="hljs-keyword">const</span> sheet = <span class="hljs-keyword">new</span> CSSStyleSheet();
sheet.replaceSync(<span class="hljs-string">`
  :host {
    display: block;
    padding: 16px;
  }
  .content {
    color: var(--text-color, #333);
  }
`</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ReusableCard</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-comment">// Use the shared stylesheet</span>
    shadow.adoptedStyleSheets = [sheet];
    shadow.innerHTML = <span class="hljs-string">`&lt;div class="content"&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/div&gt;`</span>;
  }
}
</code></pre>
<p>Google's Material Web Components use this pattern for performance optimization.</p>
<p>To better understand the performance benefits of Constructable Stylesheets, this interactive demo demonstrates the difference between traditional inline styles (duplicated for each component) and shared stylesheets (reused across all components):</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/OPJBeyP">https://codepen.io/mnichols08/pen/OPJBeyP</a></div>
<p> </p>
<p>Try adding multiple components with both methods to see how Constructable Stylesheets significantly reduce memory usage and improve performance as the number of components increases. This technique is especially valuable in applications like YouTube, where dozens or hundreds of component instances might appear on a single page.</p>
<h2 id="heading-accessibility-in-production-web-components">Accessibility in Production Web Components</h2>
<p>Successful Web Components in production prioritize accessibility:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccessibleTabPanel</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;div class="tabs" role="tablist"&gt;
        &lt;slot name="tab"&gt;&lt;/slot&gt;
      &lt;/div&gt;
      &lt;div class="panels"&gt;
        &lt;slot name="panel"&gt;&lt;/slot&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-comment">// Add keyboard navigation, ARIA attributes, etc.</span>
  }

  connectedCallback() {
    <span class="hljs-comment">// Set up accessibility requirements</span>
    <span class="hljs-keyword">const</span> tabs = <span class="hljs-built_in">this</span>.querySelectorAll(<span class="hljs-string">'[slot="tab"]'</span>);
    <span class="hljs-keyword">const</span> panels = <span class="hljs-built_in">this</span>.querySelectorAll(<span class="hljs-string">'[slot="panel"]'</span>);

    tabs.forEach(<span class="hljs-function">(<span class="hljs-params">tab, index</span>) =&gt;</span> {
      <span class="hljs-comment">// Set ARIA attributes</span>
      tab.setAttribute(<span class="hljs-string">'role'</span>, <span class="hljs-string">'tab'</span>);
      tab.setAttribute(<span class="hljs-string">'aria-selected'</span>, index === <span class="hljs-number">0</span> ? <span class="hljs-string">'true'</span> : <span class="hljs-string">'false'</span>);
      tab.setAttribute(<span class="hljs-string">'tabindex'</span>, index === <span class="hljs-number">0</span> ? <span class="hljs-string">'0'</span> : <span class="hljs-string">'-1'</span>);
      tab.setAttribute(<span class="hljs-string">'aria-controls'</span>, <span class="hljs-string">`panel-<span class="hljs-subst">${index}</span>`</span>);

      <span class="hljs-comment">// Set panel attributes</span>
      panels[index].setAttribute(<span class="hljs-string">'role'</span>, <span class="hljs-string">'tabpanel'</span>);
      panels[index].setAttribute(<span class="hljs-string">'id'</span>, <span class="hljs-string">`panel-<span class="hljs-subst">${index}</span>`</span>);
      panels[index].setAttribute(<span class="hljs-string">'aria-labelledby'</span>, tab.id);
      panels[index].hidden = index !== <span class="hljs-number">0</span>;

      <span class="hljs-comment">// Add event listeners for keyboard navigation</span>
      <span class="hljs-comment">// ...</span>
    });
  }
}
</code></pre>
<p>Adobe's Spectrum Web Components and Microsoft's FAST components both implement comprehensive accessibility features like this.</p>
<p>Accessibility is a crucial aspect of web components. Let's explore an accessible tab panel component that demonstrates proper keyboard navigation and ARIA attributes:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/QwWZXLE">https://codepen.io/mnichols08/pen/QwWZXLE</a></div>
<p> </p>
<p>This accessible tab panel implementation showcases several key accessibility features:</p>
<ol>
<li><p><strong>Proper ARIA roles and attributes</strong> - Using <code>role="tablist"</code>, <code>role="tab"</code>, and <code>role="tabpanel"</code> with appropriate relationships</p>
</li>
<li><p><strong>Keyboard navigation</strong> - Arrow keys to move between tabs, Home/End to jump to first/last tab</p>
</li>
<li><p><strong>Focus management</strong> - Visible focus indicators and proper focus order</p>
</li>
<li><p><strong>Screen reader support</strong> - Appropriate announcements when tabs change</p>
</li>
</ol>
<p>Try navigating the tabs using only your keyboard. Press Tab to focus on the tab list, then use arrow keys to move between tabs. Notice how the focus is visually indicated and how the selected tab's content is displayed.</p>
<p>This implementation follows the <a target="_blank" href="https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/">WAI-ARIA Authoring Practices</a> <a target="_blank" href="https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/">for tab panels,</a> ensuring that users of assistive technologies can effectively interact with the component.</p>
<h2 id="heading-integration-with-frameworks">Integration with Frameworks</h2>
<p>Many organizations need to integrate Web Components with existing framework codebases:</p>
<h3 id="heading-react-integration">React Integration</h3>
<pre><code class="lang-jsx"><span class="hljs-comment">// React wrapper around a Web Component</span>
<span class="hljs-keyword">import</span> React, { useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'my-web-components/dist/data-chart.js'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">DataChartWrapper</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-keyword">const</span> chartRef = useRef(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Pass complex data as properties</span>
    <span class="hljs-keyword">if</span> (chartRef.current) {
      chartRef.current.data = props.data;
    }
  }, [props.data]);

  <span class="hljs-comment">// Pass simple values as attributes</span>
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">data-chart</span>
      <span class="hljs-attr">ref</span>=<span class="hljs-string">{chartRef}</span>
      <span class="hljs-attr">theme</span>=<span class="hljs-string">{props.theme}</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">{props.height}</span>
      <span class="hljs-attr">onChartUpdate</span>=<span class="hljs-string">{props.onUpdate}</span>
    /&gt;</span></span>
  );
}
</code></pre>
<p>Salesforce uses this pattern to integrate their Lightning Web Components with React applications.</p>
<p>To see how React and Web Components work together in practice, here's an interactive demo showing the integration patterns in action:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/ByaqgoN">https://codepen.io/mnichols08/pen/ByaqgoN</a></div>
<p> </p>
<p>This demo showcases the key integration patterns described earlier:</p>
<ol>
<li><p><strong>Using refs to access Web Component properties</strong> - React uses a ref to directly set properties on the Web Component</p>
</li>
<li><p><strong>Event-based communication</strong> - The Web Component dispatches custom events that React listens for</p>
</li>
<li><p><strong>Bidirectional data flow</strong> - Changes in either React or the Web Component are synchronized</p>
</li>
</ol>
<p>Try changing the color or clicking the buttons on either side to see how React and the Web Component communicate. Notice how property changes flow from React to the Web Component, while events flow from the Web Component back to React.</p>
<h2 id="heading-micro-frontend-architectures-with-web-components">Micro-Frontend Architectures with Web Components</h2>
<p>Organizations like ING Bank have implemented micro-frontend architectures using Web Components:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Shell application --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Banking Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- Import shared components --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/shared/components/ing-header.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/shared/components/ing-footer.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- Shared header component owned by core team --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ing-header</span> <span class="hljs-attr">user-id</span>=<span class="hljs-string">"12345"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ing-header</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Micro-frontend container --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Accounts micro-frontend owned by accounts team --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">accounts-overview</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">accounts-overview</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Investments micro-frontend owned by investments team --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">investments-summary</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">investments-summary</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Loans micro-frontend owned by loans team --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">loans-overview</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">loans-overview</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Shared footer component --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ing-footer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ing-footer</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Load micro-frontends --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/accounts/components/accounts-overview.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/investments/components/investments-summary.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/loans/components/loans-overview.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This approach allows different teams to develop and deploy independently while maintaining a cohesive user experience.</p>
<p>Let's visualize how a micro-frontend architecture might work in practice with this interactive demo:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/wBvYLKK">https://codepen.io/mnichols08/pen/wBvYLKK</a></div>
<p> </p>
<p>This simplified banking portal demonstrates the core principles of micro-frontend architecture:</p>
<ol>
<li><p><strong>Team Ownership Separation</strong> - Each colored section is owned by a different team</p>
</li>
<li><p><strong>Independent Development</strong> - Each team can build and deploy their component independently</p>
</li>
<li><p><strong>Cross-Team Communication</strong> - Components interact through a centralized event bus</p>
</li>
<li><p><strong>Consistent User Experience</strong> - Despite being developed separately, components provide a unified experience</p>
</li>
</ol>
<p>Try interacting with different components and watch the event log to see how they communicate. When you click on an account in the Accounts section, notice how the Support widget responds with contextual information - demonstrating cross-component communication.</p>
<p>This pattern is similar to ING Bank's approach, where different teams own distinct parts of the application while maintaining a cohesive user experience through standardized design systems and communication protocols.</p>
<h2 id="heading-future-trends">Future Trends</h2>
<p>Based on real-world adoptions, several trends are emerging:</p>
<ol>
<li><p><strong>Server-Side Rendering with Declarative Shadow DOM</strong> - Companies like Salesforce and Google are exploring SSR for Web Components.</p>
</li>
<li><p><strong>Web Components in Design Systems</strong> - More organizations are standardizing their design systems as Web Components.</p>
</li>
<li><p><strong>Framework Integration Tools</strong> - Better tools for seamless integration with React, Angular, and Vue are being developed.</p>
</li>
<li><p><strong>Performance Optimizations</strong> - Techniques like Constructable Stylesheets and lazy-loading are becoming standard practice.</p>
</li>
<li><p><strong>Web Component Marketplaces</strong> - More robust ecosystems for sharing and discovering components are emerging.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Web Components have moved beyond theory into practical, production-ready solutions embraced by major tech companies. The patterns established by these implementations provide valuable blueprints for organizations looking to create scalable, maintainable component libraries that work across frameworks and technologies.</p>
<p>By examining real-world examples from Google, Microsoft, Adobe, and others, we can see that Web Components excel in scenarios requiring reusability, framework-agnosticism, and long-term stability. Whether you're building a design system, implementing micro-frontends, or simply creating UI widgets, these production-tested patterns can guide your implementation.</p>
<p>As browser support and tooling continue to improve, we can expect Web Components to become an even more essential part of the web development ecosystem.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components on MDN</a></p>
</li>
<li><p><a target="_blank" href="https://lit.dev/">Google's Lit library</a></p>
</li>
<li><p><a target="_blank" href="https://www.fast.design/">Microsoft's FAST components</a></p>
</li>
<li><p><a target="_blank" href="https://opensource.adobe.com/spectrum-web-components/">Adobe's Spectrum Web Components</a></p>
</li>
<li><p><a target="_blank" href="https://shoelace.style/">Shoelace components</a></p>
</li>
<li><p><a target="_blank" href="https://www.webcomponents.org/">WebComponents.org</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Accessibility in Web Components]]></title><description><![CDATA[Learn to create accessible web components using semantic HTML, ARIA roles, and keyboard navigation with interactive examples and practical strategies]]></description><link>https://journeytocode.hashnode.dev/accessibility-in-web-components</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/accessibility-in-web-components</guid><category><![CDATA[Accessibility]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[beginner]]></category><category><![CDATA[SEO]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 30 Mar 2025 16:00:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742851224398/6ccb60cc-4561-4374-bf9f-6956848355f3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Meet Sarah, a web user who navigates the internet using a screen reader. When she encounters custom elements on websites, her experience isn't determined by visual design—it's determined by whether developers prioritized accessibility. When developers overlook the needs of millions of users with disabilities, they transform bridges into barriers, creating frustration instead of inclusive solutions.</p>
<p>Web components unlock a world of possibilities, empowering developers to craft highly customizable, reusable elements that elevate both modularity and maintainability. Yet their true power lies not just in technical elegance, but in their ability to serve all users. This guide will take you through essential strategies for creating truly inclusive web components—exploring semantic HTML, ARIA roles, keyboard navigation, and robust testing methodologies. You'll find interactive examples throughout that invite you to experiment, transforming abstract concepts into tangible skills you can immediately apply.</p>
<p>With these fundamentals in mind, let's see accessibility in action.</p>
<h2 id="heading-the-accessibility-difference">The Accessibility Difference</h2>
<p>The following examples demonstrate how seemingly small details create dramatically different user experiences:</p>
<p>Before diving into details, let's visualize the difference accessibility makes:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/yyLEWeX">https://codepen.io/mnichols08/pen/yyLEWeX</a></div>
<p>Try using only your keyboard (Tab and Enter) to interact with both buttons above. Notice how the accessible version provides:</p>
<ul>
<li>Clear focus indication</li>
<li>Proper state announcement  </li>
<li>Keyboard operability</li>
</ul>
<p>These seemingly small details make a tremendous difference for users relying on assistive technologies.</p>
<h2 id="heading-understanding-accessibility-in-web-components">Understanding Accessibility in Web Components</h2>
<p>Now that we've seen the tangible impact of accessibility features, let's explore the core principles that make web components truly inclusive for all users.</p>
<p>Accessibility (often abbreviated as A11y) involves designing products usable by people with disabilities. For Web Components, this means ensuring custom elements are perceivable, operable, understandable, and robust for all users.</p>
<p><strong>Key Considerations:</strong></p>
<ul>
<li><strong>Semantic HTML:</strong> Use appropriate HTML elements to convey meaning, as browsers and assistive technologies rely on these semantics.</li>
<li><strong>ARIA Roles and Properties:</strong> When native HTML elements are insufficient, ARIA roles and properties provide additional accessibility information.</li>
<li><strong>Keyboard Accessibility:</strong> Ensure all interactive elements are navigable and operable via keyboard.</li>
<li><strong>Focus Management:</strong> Properly manage focus to assist users navigating with keyboards or assistive devices.</li>
</ul>
<p>While these accessibility considerations apply to all web development, Web Components introduce unique challenges through their use of Shadow DOM—an essential feature that requires thoughtful implementation.</p>
<h3 id="heading-the-shadow-dom-challenge">The Shadow DOM Challenge</h3>
<p>Web Components use Shadow DOM to encapsulate styles and functionality, creating unique accessibility challenges:</p>
<ol>
<li><strong>Style Isolation:</strong> Focus styles might not be visible if they rely on global CSS</li>
<li><strong>DOM Isolation:</strong> ARIA relationships across shadow boundaries might not work as expected</li>
<li><strong>Event Handling:</strong> Custom events must properly handle keyboard interactions</li>
</ol>
<p>Despite these challenges, you can create fully accessible Web Components with the right approach. Let's explore practical implementation strategies that address these concerns while maintaining the benefits of encapsulation.</p>
<h2 id="heading-implementing-accessibility-in-web-components">Implementing Accessibility in Web Components</h2>
<p>These challenges can be overcome with proper implementation, as demonstrated in the examples below.</p>
<h3 id="heading-1-semantic-html-and-aria-roles">1. Semantic HTML and ARIA Roles</h3>
<p>Start with native HTML elements that inherently provide accessibility. When creating custom elements, assign appropriate ARIA roles and properties to convey their purpose and state.</p>
<h4 id="heading-interactive-example-accessible-toggle-button">Interactive Example: Accessible Toggle Button</h4>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/YPzvbaK">https://codepen.io/mnichols08/pen/YPzvbaK</a></div>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccessibleToggle</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-built_in">this</span>.pressed = <span class="hljs-literal">false</span>;
    <span class="hljs-built_in">this</span>.render();
  }

  connectedCallback() {
    <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'button'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.toggle());
    <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'button'</span>).addEventListener(<span class="hljs-string">'keydown'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Enter'</span> || e.key === <span class="hljs-string">' '</span>) {
        e.preventDefault();
        <span class="hljs-built_in">this</span>.toggle();
      }
    });
  }

  toggle() {
    <span class="hljs-built_in">this</span>.pressed = !<span class="hljs-built_in">this</span>.pressed;
    <span class="hljs-built_in">this</span>.render();
    <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'toggle'</span>, { 
      <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">detail</span>: { <span class="hljs-attr">pressed</span>: <span class="hljs-built_in">this</span>.pressed }
    }));
  }

  render() {
    <span class="hljs-keyword">const</span> button = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'button'</span>) || <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'button'</span>);
    button.setAttribute(<span class="hljs-string">'role'</span>, <span class="hljs-string">'button'</span>);
    button.setAttribute(<span class="hljs-string">'aria-pressed'</span>, <span class="hljs-built_in">this</span>.pressed);
    button.setAttribute(<span class="hljs-string">'aria-label'</span>, <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'label'</span>) || <span class="hljs-string">'Toggle'</span>);
    button.innerHTML = <span class="hljs-built_in">this</span>.pressed ? <span class="hljs-string">'ON'</span> : <span class="hljs-string">'OFF'</span>;
    button.className = <span class="hljs-built_in">this</span>.pressed ? <span class="hljs-string">'pressed'</span> : <span class="hljs-string">''</span>;

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'button'</span>)) {
      <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
        &lt;style&gt;
          button {
            padding: 8px 16px;
            border: 2px solid #333;
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
            transition: all 0.3s;
          }
          button:focus-visible {
            outline: 3px solid blue;
            outline-offset: 2px;
          }
          button.pressed {
            background-color: #4CAF50;
            color: white;
          }
          button:not(.pressed) {
            background-color: #f1f1f1;
          }
        &lt;/style&gt;
      `</span>;
      <span class="hljs-built_in">this</span>.shadowRoot.appendChild(button);
    }
  }
}

customElements.define(<span class="hljs-string">'accessible-toggle'</span>, AccessibleToggle);
</code></pre>
<p><strong>Key accessibility features:</strong></p>
<ul>
<li>Uses <code>aria-pressed</code> to convey state to assistive technologies</li>
<li>Provides visible state indication through color and text changes</li>
<li>Ensures keyboard operability with Enter and Space keys</li>
<li>Implements strong focus indication with high contrast outline</li>
<li>Dispatches events that can be caught by parent components</li>
</ul>
<p>Try navigating to the button with Tab and activating it with Enter or Space. A screen reader would announce: "Toggle button, not pressed" and after activation: "Toggle button, pressed."</p>
<h3 id="heading-2-keyboard-accessibility-and-focus-management">2. Keyboard Accessibility and Focus Management</h3>
<p>With the foundation of proper semantics established, let's focus on how users navigate through interactive components without a mouse.</p>
<p>Proper focus management is crucial, especially with dynamic content or modal dialogs. Users navigating with keyboards need clear focus indication and logical tab order.</p>
<h4 id="heading-interactive-example-accessible-modal-dialog">Interactive Example: Accessible Modal Dialog</h4>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/raNKgXo">https://codepen.io/mnichols08/pen/raNKgXo</a></div>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccessibleModal</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-built_in">this</span>._previouslyFocusedElement = <span class="hljs-literal">null</span>;
    <span class="hljs-built_in">this</span>.render();
  }

  connectedCallback() {
    <span class="hljs-keyword">const</span> trigger = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'#modal-trigger'</span>);
    <span class="hljs-keyword">const</span> closeBtn = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'#modal-close'</span>);
    <span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'[role="dialog"]'</span>);

    trigger.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.open());
    closeBtn.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.close());

    <span class="hljs-comment">// Close on Escape key</span>
    <span class="hljs-built_in">this</span>.addEventListener(<span class="hljs-string">'keydown'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
      <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Escape'</span> &amp;&amp; !dialog.hasAttribute(<span class="hljs-string">'aria-hidden'</span>)) {
        <span class="hljs-built_in">this</span>.close();
      }
    });

    <span class="hljs-comment">// Close when clicking outside the modal</span>
    <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.modal-overlay'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
      <span class="hljs-keyword">if</span> (e.target.classList.contains(<span class="hljs-string">'modal-overlay'</span>)) {
        <span class="hljs-built_in">this</span>.close();
      }
    });
  }

  open() {
    <span class="hljs-built_in">this</span>._previouslyFocusedElement = <span class="hljs-built_in">document</span>.activeElement;
    <span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'[role="dialog"]'</span>);
    dialog.removeAttribute(<span class="hljs-string">'aria-hidden'</span>);
    <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.modal-overlay'</span>).style.display = <span class="hljs-string">'flex'</span>;
    <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'#modal-close'</span>).focus();

    <span class="hljs-comment">// Trap focus inside modal</span>
    <span class="hljs-built_in">this</span>._handleFocusTrap();
  }

  close() {
    <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'[role="dialog"]'</span>).setAttribute(<span class="hljs-string">'aria-hidden'</span>, <span class="hljs-string">'true'</span>);
    <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.modal-overlay'</span>).style.display = <span class="hljs-string">'none'</span>;

    <span class="hljs-comment">// Restore focus</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>._previouslyFocusedElement) {
      <span class="hljs-built_in">this</span>._previouslyFocusedElement.focus();
    }
  }

  _handleFocusTrap() {
    <span class="hljs-keyword">const</span> focusableElements = <span class="hljs-built_in">this</span>.shadowRoot.querySelectorAll(<span class="hljs-string">'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'</span>);
    <span class="hljs-keyword">const</span> firstFocusable = focusableElements[<span class="hljs-number">0</span>];
    <span class="hljs-keyword">const</span> lastFocusable = focusableElements[focusableElements.length - <span class="hljs-number">1</span>];

    <span class="hljs-built_in">this</span>.shadowRoot.addEventListener(<span class="hljs-string">'keydown'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
      <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Tab'</span>) {
        <span class="hljs-keyword">if</span> (e.shiftKey &amp;&amp; <span class="hljs-built_in">document</span>.activeElement === firstFocusable) {
          e.preventDefault();
          lastFocusable.focus();
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (!e.shiftKey &amp;&amp; <span class="hljs-built_in">document</span>.activeElement === lastFocusable) {
          e.preventDefault();
          firstFocusable.focus();
        }
      }
    });
  }

  render() {
    <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        .modal-overlay {
          position: fixed;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          background-color: rgba(0, 0, 0, 0.5);
          display: none;
          justify-content: center;
          align-items: center;
          z-index: 1000;
        }

        .modal-content {
          background-color: white;
          padding: 20px;
          border-radius: 4px;
          max-width: 500px;
          width: 90%;
          box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        }

        button {
          padding: 8px 16px;
          cursor: pointer;
          border-radius: 4px;
        }

        button:focus-visible {
          outline: 3px solid blue;
          outline-offset: 2px;
        }

        #modal-close {
          float: right;
          background: none;
          border: 1px solid #ccc;
          font-size: 16px;
        }

        #modal-trigger {
          background-color: #4CAF50;
          color: white;
          border: none;
          font-size: 16px;
        }
      &lt;/style&gt;

      &lt;button id="modal-trigger"&gt;Open Modal&lt;/button&gt;

      &lt;div class="modal-overlay"&gt;
        &lt;div role="dialog" aria-labelledby="modal-title" aria-modal="true" aria-hidden="true" class="modal-content"&gt;
          &lt;button id="modal-close" aria-label="Close dialog"&gt;×&lt;/button&gt;
          &lt;h2 id="modal-title"&gt;Accessible Modal Dialog&lt;/h2&gt;
          &lt;p&gt;This is an example of an accessible modal dialog component.&lt;/p&gt;
          &lt;p&gt;It traps focus inside the modal, can be closed with the ESC key, and restores focus when closed.&lt;/p&gt;
          &lt;button&gt;Test Button 1&lt;/button&gt;
          &lt;button&gt;Test Button 2&lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'accessible-modal'</span>, AccessibleModal);
</code></pre>
<p><strong>Key accessibility features:</strong></p>
<ul>
<li>Focus trap within the modal (users can't tab out accidentally)</li>
<li>Keyboard closing with Escape key</li>
<li>Focus restoration when modal closes</li>
<li>Proper ARIA roles (<code>dialog</code>, <code>aria-labelledby</code>, <code>aria-modal</code>)</li>
<li>Clear visual focus indicators</li>
</ul>
<p>Try opening the modal and navigating with Tab. Notice how focus is trapped inside the modal and restored to the trigger button when closed.</p>
<h3 id="heading-3-complex-components-and-keyboard-navigation">3. Complex Components and Keyboard Navigation</h3>
<p>While proper keyboard navigation creates a solid foundation, complex components like tabs, accordions, and carousels require specialized interaction patterns to maintain usability.</p>
<h4 id="heading-interactive-example-accessible-tabs">Interactive Example: Accessible Tabs</h4>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/yyLEdyx">https://codepen.io/mnichols08/pen/yyLEdyx</a></div>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccessibleTabs</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-built_in">this</span>.render();
  }

  connectedCallback() {
    <span class="hljs-keyword">const</span> tabs = <span class="hljs-built_in">Array</span>.from(<span class="hljs-built_in">this</span>.shadowRoot.querySelectorAll(<span class="hljs-string">'[role="tab"]'</span>));
    <span class="hljs-keyword">const</span> panels = <span class="hljs-built_in">Array</span>.from(<span class="hljs-built_in">this</span>.shadowRoot.querySelectorAll(<span class="hljs-string">'[role="tabpanel"]'</span>));

    <span class="hljs-comment">// Select first tab by default</span>
    <span class="hljs-built_in">this</span>.selectTab(tabs[<span class="hljs-number">0</span>]);

    tabs.forEach(<span class="hljs-function"><span class="hljs-params">tab</span> =&gt;</span> {
      tab.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.selectTab(tab));

      tab.addEventListener(<span class="hljs-string">'keydown'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
        <span class="hljs-comment">// Arrow key navigation</span>
        <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'ArrowRight'</span> || e.key === <span class="hljs-string">'ArrowLeft'</span>) {
          e.preventDefault();

          <span class="hljs-keyword">const</span> currentIndex = tabs.indexOf(tab);
          <span class="hljs-keyword">let</span> nextIndex;

          <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'ArrowRight'</span>) {
            nextIndex = (currentIndex + <span class="hljs-number">1</span>) % tabs.length;
          } <span class="hljs-keyword">else</span> {
            nextIndex = (currentIndex - <span class="hljs-number">1</span> + tabs.length) % tabs.length;
          }

          tabs[nextIndex].focus();
          <span class="hljs-built_in">this</span>.selectTab(tabs[nextIndex]);
        }

        <span class="hljs-comment">// Home and End keys for first/last tabs</span>
        <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Home'</span>) {
          e.preventDefault();
          tabs[<span class="hljs-number">0</span>].focus();
          <span class="hljs-built_in">this</span>.selectTab(tabs[<span class="hljs-number">0</span>]);
        }

        <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'End'</span>) {
          e.preventDefault();
          tabs[tabs.length - <span class="hljs-number">1</span>].focus();
          <span class="hljs-built_in">this</span>.selectTab(tabs[tabs.length - <span class="hljs-number">1</span>]);
        }
      });
    });
  }

  selectTab(selectedTab) {
    <span class="hljs-keyword">const</span> tabs = <span class="hljs-built_in">Array</span>.from(<span class="hljs-built_in">this</span>.shadowRoot.querySelectorAll(<span class="hljs-string">'[role="tab"]'</span>));
    <span class="hljs-keyword">const</span> panels = <span class="hljs-built_in">Array</span>.from(<span class="hljs-built_in">this</span>.shadowRoot.querySelectorAll(<span class="hljs-string">'[role="tabpanel"]'</span>));

    tabs.forEach(<span class="hljs-function"><span class="hljs-params">tab</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> selected = tab === selectedTab;
      tab.setAttribute(<span class="hljs-string">'aria-selected'</span>, selected);
      tab.setAttribute(<span class="hljs-string">'tabindex'</span>, selected ? <span class="hljs-string">'0'</span> : <span class="hljs-string">'-1'</span>);
    });

    panels.forEach(<span class="hljs-function"><span class="hljs-params">panel</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> isSelected = panel.id === selectedTab.getAttribute(<span class="hljs-string">'aria-controls'</span>);
      panel.hidden = !isSelected;
    });
  }

  render() {
    <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        .tabs {
          display: flex;
          border-bottom: 1px solid #ccc;
        }

        [role="tab"] {
          padding: 10px 15px;
          margin-right: 5px;
          background-color: #f1f1f1;
          border: 1px solid #ccc;
          border-bottom: none;
          border-radius: 4px 4px 0 0;
          cursor: pointer;
          position: relative;
          top: 1px;
        }

        [role="tab"][aria-selected="true"] {
          background-color: white;
          border-bottom: 1px solid white;
        }

        [role="tab"]:focus-visible {
          outline: 3px solid blue;
          outline-offset: 2px;
        }

        [role="tabpanel"] {
          padding: 20px;
          border: 1px solid #ccc;
          border-top: none;
        }
      &lt;/style&gt;

      &lt;div class="tabs-container"&gt;
        &lt;div role="tablist" class="tabs" aria-label="Programming information"&gt;
          &lt;button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1" tabindex="0"&gt;HTML&lt;/button&gt;
          &lt;button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1"&gt;CSS&lt;/button&gt;
          &lt;button role="tab" id="tab-3" aria-selected="false" aria-controls="panel-3" tabindex="-1"&gt;JavaScript&lt;/button&gt;
        &lt;/div&gt;

        &lt;div role="tabpanel" id="panel-1" aria-labelledby="tab-1"&gt;
          &lt;h3&gt;HTML&lt;/h3&gt;
          &lt;p&gt;HTML (HyperText Markup Language) is the standard markup language for documents designed to be displayed in a web browser.&lt;/p&gt;
        &lt;/div&gt;

        &lt;div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden&gt;
          &lt;h3&gt;CSS&lt;/h3&gt;
          &lt;p&gt;CSS (Cascading Style Sheets) is a style sheet language used for describing the presentation of a document written in HTML.&lt;/p&gt;
        &lt;/div&gt;

        &lt;div role="tabpanel" id="panel-3" aria-labelledby="tab-3" hidden&gt;
          &lt;h3&gt;JavaScript&lt;/h3&gt;
          &lt;p&gt;JavaScript is a programming language that enables interactive web pages and is an essential part of web applications.&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'accessible-tabs'</span>, AccessibleTabs);
</code></pre>
<p><strong>Key accessibility features:</strong></p>
<ul>
<li>Proper ARIA roles for <code>tablist</code>, <code>tab</code>, and <code>tabpanel</code></li>
<li>Keyboard navigation with arrow keys, Home, and End</li>
<li>Single tab stop in the tab order with arrow key navigation between tabs</li>
<li>Proper state management with <code>aria-selected</code> and <code>tabindex</code></li>
<li>Clear relationship between tabs and panels using <code>aria-controls</code> and <code>aria-labelledby</code></li>
</ul>
<p>Try using the arrow keys to navigate between tabs. Notice how this follows the expected keyboard interaction pattern for tabs.</p>
<h3 id="heading-4-announcements-for-dynamic-content">4. Announcements for Dynamic Content</h3>
<p>Beyond static structure and interaction, many modern interfaces feature dynamic content that changes without page reloads. This presents another accessibility challenge: how do we keep non-visual users informed of these changes?</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/OPJEeXj">https://codepen.io/mnichols08/pen/OPJEeXj</a></div>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LiveRegion</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-built_in">this</span>.render();
  }

  connectedCallback() {
    <span class="hljs-keyword">const</span> button = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'button'</span>);
    <span class="hljs-keyword">const</span> liveRegion = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'[aria-live]'</span>);
    <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span>;

    button.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
      count++;
      liveRegion.textContent = <span class="hljs-string">`Button clicked <span class="hljs-subst">${count}</span> time<span class="hljs-subst">${count === <span class="hljs-number">1</span> ? <span class="hljs-string">''</span> : <span class="hljs-string">'s'</span>}</span>`</span>;
    });
  }

  render() {
    <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        button {
          padding: 8px 16px;
          background-color: #4CAF50;
          color: white;
          border: none;
          border-radius: 4px;
          cursor: pointer;
        }

        button:focus-visible {
          outline: 3px solid blue;
          outline-offset: 2px;
        }

        .live-region {
          margin-top: 10px;
          padding: 10px;
          background-color: #f8f8f8;
          border-radius: 4px;
        }
      &lt;/style&gt;

      &lt;div&gt;
        &lt;button&gt;Increment Counter&lt;/button&gt;
        &lt;div class="live-region" aria-live="polite"&gt;Counter starts at 0&lt;/div&gt;
      &lt;/div&gt;
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'live-region'</span>, LiveRegion);
</code></pre>
<p><strong>Key accessibility feature:</strong></p>
<ul>
<li>Uses <code>aria-live="polite"</code> to announce changes to screen readers without interrupting current announcements</li>
</ul>
<h2 id="heading-testing-for-accessibility">Testing for Accessibility</h2>
<p>Implementing these accessibility features is only half the journey. To ensure your components work reliably for all users, you need a systematic approach to testing. Let's explore the tools and techniques that will help you verify your implementation meets accessibility standards.</p>
<h3 id="heading-1-automated-testing">1. Automated Testing</h3>
<p>Utilize tools like <a target="_blank" href="https://www.deque.com/axe/">axe-core</a> or <a target="_blank" href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Example of using axe-core for accessibility testing</span>
<span class="hljs-keyword">import</span> axe <span class="hljs-keyword">from</span> <span class="hljs-string">'axe-core'</span>;

<span class="hljs-comment">// Test a specific web component</span>
axe.run(<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'accessible-tabs'</span>), <span class="hljs-function">(<span class="hljs-params">err, results</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Accessibility testing failed:'</span>, err);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Accessibility test results:'</span>, results);
  }
});
</code></pre>
<h2 id="heading-what-is-lighthouse">What is Lighthouse?</h2>
<p>Lighthouse is an open-source automated tool developed by Google that audits web pages for performance, accessibility, progressive web apps, SEO, and more.</p>
<h2 id="heading-getting-started-with-lighthouse">Getting Started with Lighthouse</h2>
<h3 id="heading-method-1-using-chrome-devtools">Method 1: Using Chrome DevTools</h3>
<ol>
<li>Open Chrome browser</li>
<li>Navigate to the webpage you want to test</li>
<li>Press F12 or right-click and select "Inspect" to open DevTools</li>
<li>Click on the "Lighthouse" tab</li>
<li>Select "Accessibility" checkbox (deselect others if you only want accessibility)</li>
<li>Click "Generate report"</li>
</ol>
<h3 id="heading-method-2-chrome-extension">Method 2: Chrome Extension</h3>
<ol>
<li>Install the Lighthouse extension from Chrome Web Store</li>
<li>Navigate to the page you want to test</li>
<li>Click the Lighthouse icon in your browser toolbar</li>
<li>Select "Accessibility" checkbox</li>
<li>Click "Generate report"</li>
</ol>
<h3 id="heading-method-3-command-line">Method 3: Command Line</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Install Lighthouse globally</span>
npm install -g lighthouse

<span class="hljs-comment"># Run Lighthouse accessibility audit</span>
lighthouse https://example.com --only-categories=accessibility --output=html --output-path=./report.html
</code></pre>
<h3 id="heading-2-manual-testing">2. Manual Testing</h3>
<p>Automated tests can't catch everything. Manual testing should include:</p>
<p><strong>Keyboard Testing:</strong></p>
<ul>
<li>Can all interactive elements be accessed via Tab key?</li>
<li>Do all controls work with keyboard alternatives (Enter, Space, arrow keys)?</li>
<li>Is there a visible focus indicator at all times?</li>
</ul>
<p><strong>Screen Reader Testing:</strong></p>
<ul>
<li>Try your components with VoiceOver (macOS), NVDA or JAWS (Windows), or TalkBack (Android)</li>
<li>Is all relevant information announced?</li>
<li>Are state changes communicated properly?</li>
</ul>
<p><strong>High Contrast Mode:</strong></p>
<ul>
<li>Test with high contrast mode enabled in your OS</li>
<li>Do all interactive elements remain visible and distinct?</li>
</ul>
<h2 id="heading-screen-reader-simulator">Screen Reader Simulator</h2>
<p>This interactive CodePen demonstrates how screen readers interpret web content, without requiring any software installation. Simply enter your HTML or use the built-in generator to compare non-semantic versus semantic markup.
See firsthand how thoughtful HTML structure dramatically changes the experience for users who rely on assistive technology. Test your own code to identify potential accessibility improvements.
Note: This simulator provides educational insights only and should not replace testing with actual screen reader technology when evaluating web accessibility.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/azbRMRM">https://codepen.io/mnichols08/pen/azbRMRM</a></div>
<h2 id="heading-guide-to-testing-with-nvda-screen-reader">Guide to Testing with NVDA Screen Reader</h2>
<p>To test with NVDA (NonVisual Desktop Access), download and install it from <a target="_blank" href="http://nvaccess.org">nvaccess.org</a>. After installation, you'll hear a startup sound and voice feedback will begin. Use the NVDA key (usually Insert) combined with other keys to navigate. Press NVDA+Space to toggle focus and browse modes when testing web applications.</p>
<p>Pay attention to how NVDA announces your interface elements. Ensure all interactive elements are properly labeled and keyboard-accessible. Test different scenarios: form completion, menu navigation, and custom component interaction. Listen for proper announcements of headings (NVDA+H to navigate), landmarks (NVDA+D), and alerts. Verify that dynamic content changes are announced and error messages are accessible.</p>
<h2 id="heading-common-accessibility-pitfalls-in-web-components">Common Accessibility Pitfalls in Web Components</h2>
<p>Even with thorough testing, certain accessibility issues frequently emerge in Web Component development. By understanding these common pitfalls, you can address them proactively in your implementation.</p>
<h3 id="heading-1-shadow-dom-isolation-issues">1. Shadow DOM Isolation Issues</h3>
<p><strong>Problem:</strong> ARIA relationships that cross shadow boundaries don't work as expected.</p>
<p><strong>Solution:</strong> Keep related elements within the same shadow root or use alternative techniques like <code>aria-live</code> regions for updates.</p>
<h3 id="heading-2-forgetting-about-non-mouse-users">2. Forgetting About Non-Mouse Users</h3>
<p><strong>Problem:</strong> Components only work with mouse events (<code>click</code>, <code>hover</code>).</p>
<p><strong>Solution:</strong> Include keyboard event handlers and touch events for mobile accessibility.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Bad example - only mouse support</span>
button.addEventListener(<span class="hljs-string">'click'</span>, handleAction);

<span class="hljs-comment">// Good example - comprehensive interaction support</span>
button.addEventListener(<span class="hljs-string">'click'</span>, handleAction);
button.addEventListener(<span class="hljs-string">'keydown'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Enter'</span> || e.key === <span class="hljs-string">' '</span>) {
    e.preventDefault();
    handleAction();
  }
});
</code></pre>
<h3 id="heading-3-poor-focus-management">3. Poor Focus Management</h3>
<p><strong>Problem:</strong> Focus gets lost during dynamic updates or when elements are removed.</p>
<p><strong>Solution:</strong> Always manage focus explicitly, especially when showing/hiding content.</p>
<h3 id="heading-4-insufficient-color-contrast">4. Insufficient Color Contrast</h3>
<p><strong>Problem:</strong> Text or UI controls don't have sufficient contrast with their backgrounds.</p>
<p><strong>Solution:</strong> Follow WCAG guidelines for contrast ratios (4.5:1 for normal text, 3:1 for large text).</p>
<p>Now that we've identified potential problems, let's consolidate what we've learned into actionable best practices that will guide your development process from ideation through implementation.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<ul>
<li><strong>Use Native Elements When Possible:</strong> Leverage native HTML elements with inherent accessibility support.</li>
<li><strong>Provide Accessible Names:</strong> Ensure all interactive elements have accessible names using <code>aria-label</code>, <code>aria-labelledby</code>, or semantic HTML.</li>
<li><strong>Ensure Focus Visibility:</strong> Maintain visible focus indicators for keyboard and assistive technology users.</li>
<li><strong>Handle Dynamic Content Carefully:</strong> When updating content, manage focus appropriately and announce changes.</li>
<li><strong>Test Regularly:</strong> Incorporate accessibility testing into your development process.</li>
<li><strong>Document Accessibility Features:</strong> Include accessibility information in your component documentation.</li>
</ul>
<p>These best practices come together most effectively when applied to real-world scenarios. Let's examine a comprehensive example that integrates multiple accessibility features into a practical, commonly-used component:</p>
<h2 id="heading-real-world-example-an-accessible-search-component">Real-World Example: An Accessible Search Component</h2>
<p>Let's combine everything we've learned into a practical example:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/raNKEMb">https://codepen.io/mnichols08/pen/raNKEMb</a></div>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccessibleSearch</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-built_in">this</span>.results = [];
    <span class="hljs-built_in">this</span>.render();
  }

  connectedCallback() {
    <span class="hljs-keyword">const</span> input = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'input'</span>);
    <span class="hljs-keyword">const</span> button = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'button'</span>);
    <span class="hljs-keyword">const</span> results = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.results'</span>);

    <span class="hljs-comment">// Example data - in a real component, this might come from an API</span>
    <span class="hljs-keyword">const</span> sampleData = [
      <span class="hljs-string">'JavaScript'</span>, <span class="hljs-string">'TypeScript'</span>, <span class="hljs-string">'HTML'</span>, <span class="hljs-string">'CSS'</span>, <span class="hljs-string">'React'</span>, 
      <span class="hljs-string">'Angular'</span>, <span class="hljs-string">'Vue'</span>, <span class="hljs-string">'Svelte'</span>, <span class="hljs-string">'Web Components'</span>, <span class="hljs-string">'Accessibility'</span>
    ];

    input.addEventListener(<span class="hljs-string">'input'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> query = input.value.toLowerCase();
      <span class="hljs-keyword">if</span> (query.length &lt; <span class="hljs-number">2</span>) {
        <span class="hljs-built_in">this</span>.results = [];
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">this</span>.results = sampleData.filter(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> 
          item.toLowerCase().includes(query)
        );
      }
      <span class="hljs-built_in">this</span>.updateResults();
    });

    button.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.performSearch();
    });

    input.addEventListener(<span class="hljs-string">'keydown'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
      <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Enter'</span>) {
        e.preventDefault();
        <span class="hljs-built_in">this</span>.performSearch();
      }

      <span class="hljs-comment">// Allow keyboard navigation through results</span>
      <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'ArrowDown'</span> &amp;&amp; <span class="hljs-built_in">this</span>.results.length &gt; <span class="hljs-number">0</span>) {
        e.preventDefault();
        <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.result-item'</span>).focus();
      }
    });

    <span class="hljs-comment">// Delegate event listener for result items</span>
    results.addEventListener(<span class="hljs-string">'keydown'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
      <span class="hljs-keyword">if</span> (!e.target.classList.contains(<span class="hljs-string">'result-item'</span>)) <span class="hljs-keyword">return</span>;

      <span class="hljs-keyword">const</span> resultItems = <span class="hljs-built_in">Array</span>.from(<span class="hljs-built_in">this</span>.shadowRoot.querySelectorAll(<span class="hljs-string">'.result-item'</span>));
      <span class="hljs-keyword">const</span> currentIndex = resultItems.indexOf(e.target);

      <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'ArrowDown'</span> &amp;&amp; currentIndex &lt; resultItems.length - <span class="hljs-number">1</span>) {
        e.preventDefault();
        resultItems[currentIndex + <span class="hljs-number">1</span>].focus();
      }

      <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'ArrowUp'</span>) {
        e.preventDefault();
        <span class="hljs-keyword">if</span> (currentIndex === <span class="hljs-number">0</span>) {
          input.focus();
        } <span class="hljs-keyword">else</span> {
          resultItems[currentIndex - <span class="hljs-number">1</span>].focus();
        }
      }

      <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Enter'</span>) {
        e.preventDefault();
        <span class="hljs-built_in">this</span>.selectResult(e.target.textContent);
      }
    });
  }

  performSearch() {
    <span class="hljs-keyword">const</span> announcement = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.screen-reader-announcement'</span>);
    <span class="hljs-keyword">const</span> resultCount = <span class="hljs-built_in">this</span>.results.length;

    announcement.textContent = <span class="hljs-string">`Found <span class="hljs-subst">${resultCount}</span> result<span class="hljs-subst">${resultCount === <span class="hljs-number">1</span> ? <span class="hljs-string">''</span> : <span class="hljs-string">'s'</span>}</span>`</span>;
  }

  selectResult(text) {
    <span class="hljs-keyword">const</span> input = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'input'</span>);
    input.value = text;
    <span class="hljs-built_in">this</span>.results = [];
    <span class="hljs-built_in">this</span>.updateResults();
    input.focus();

    <span class="hljs-comment">// Announce selection to screen readers</span>
    <span class="hljs-keyword">const</span> announcement = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.screen-reader-announcement'</span>);
    announcement.textContent = <span class="hljs-string">`Selected <span class="hljs-subst">${text}</span>`</span>;
  }

  updateResults() {
    <span class="hljs-keyword">const</span> resultsContainer = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'.results'</span>);

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.results.length === <span class="hljs-number">0</span>) {
      resultsContainer.innerHTML = <span class="hljs-string">''</span>;
      resultsContainer.setAttribute(<span class="hljs-string">'aria-hidden'</span>, <span class="hljs-string">'true'</span>);
      <span class="hljs-keyword">return</span>;
    }

    resultsContainer.removeAttribute(<span class="hljs-string">'aria-hidden'</span>);
    resultsContainer.innerHTML = <span class="hljs-built_in">this</span>.results.map(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> 
      <span class="hljs-string">`&lt;button class="result-item" tabindex="0"&gt;<span class="hljs-subst">${result}</span>&lt;/button&gt;`</span>
    ).join(<span class="hljs-string">''</span>);
  }

  render() {
    <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        .search-container {
          position: relative;
          width: 300px;
        }

        .search-input {
          display: flex;
        }

        input {
          flex: 1;
          padding: 8px;
          border: 2px solid #ccc;
          border-radius: 4px 0 0 4px;
          font-size: 16px;
        }

        input:focus {
          outline: none;
          border-color: blue;
        }

        button {
          padding: 8px 16px;
          background-color: #4CAF50;
          color: white;
          border: none;
          border-radius: 0 4px 4px 0;
          cursor: pointer;
        }

        button:focus-visible {
          outline: 3px solid blue;
          outline-offset: 2px;
        }

        .results {
          position: absolute;
          top: 100%;
          left: 0;
          right: 0;
          z-index: 10;
          background: white;
          border: 1px solid #ccc;
          border-top: none;
          max-height: 200px;
          overflow-y: auto;
        }

        .result-item {
          display: block;
          width: 100%;
          text-align: left;
          padding: 8px;
          background: none;
          border: none;
          border-bottom: 1px solid #eee;
          cursor: pointer;
        }

        .result-item:hover, .result-item:focus {
          background-color: #f1f1f1;
        }

        .screen-reader-only {
          position: absolute;
          width: 1px;
          height: 1px;
          padding: 0;
          margin: -1px;
          overflow: hidden;
          clip: rect(0, 0, 0, 0);
          white-space: nowrap;
          border-width: 0;
        }
      &lt;/style&gt;

      &lt;div class="search-container"&gt;
        &lt;label id="search-label" for="search-input"&gt;Search&lt;/label&gt;
        &lt;div class="search-input"&gt;
          &lt;input 
            id="search-input" 
            type="text" 
            aria-labelledby="search-label" 
            aria-autocomplete="list"
            aria-controls="search-results"
            autocomplete="off"
          /&gt;
          &lt;button aria-label="Search"&gt;Go&lt;/button&gt;
        &lt;/div&gt;

        &lt;div 
          id="search-results" 
          class="results" 
          role="listbox" 
          aria-label="Search suggestions"
          aria-hidden="true"
        &gt;&lt;/div&gt;

        &lt;div 
          class="screen-reader-announcement screen-reader-only" 
          aria-live="polite"
        &gt;&lt;/div&gt;
      &lt;/div&gt;
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'accessible-search'</span>, AccessibleSearch);
</code></pre>
<p><strong>Key accessibility features:</strong></p>
<ul>
<li>Proper labeling of all elements</li>
<li>Keyboard navigation through results with arrow keys</li>
<li>Proper ARIA roles and relationships</li>
<li>Live region announcements for search results and selections</li>
<li>Full keyboard functionality without requiring a mouse</li>
</ul>
<p>As this search component demonstrates, accessible Web Components require attention to multiple factors—from semantic structure to keyboard interactions and dynamic announcements. By applying these techniques to your own components, you'll create experiences that work for everyone.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating accessible Web Components isn't just about compliance—it's about creating a better web for everyone. By incorporating these practices from the start, you'll build components that are more robust, user-friendly, and inclusive.</p>
<p>Remember these key principles:</p>
<ol>
<li>Use semantic HTML whenever possible</li>
<li>Implement proper keyboard support</li>
<li>Manage focus explicitly</li>
<li>Announce dynamic changes with ARIA live regions</li>
<li>Test thoroughly with both automated and manual methods</li>
</ol>
<p>By building accessibility into your Web Components from the ground up, you're not just helping users with disabilities—you're creating a better experience for everyone.</p>
<hr />
<h2 id="heading-resources">Resources</h2>
<ul>
<li><a target="_blank" href="https://www.w3.org/WAI/">Web Accessibility Initiative (WAI)</a></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">MDN Web Components Guide</a></li>
<li><a target="_blank" href="https://www.w3.org/WAI/ARIA/apg/">ARIA Authoring Practices Guide</a></li>
<li><a target="_blank" href="https://github.com/mnichols08/WebComponentAccesibility/">GitHub repository containing these examples</a></li>
<li><a target="_blank" href="https://mnichols08.github.io/WebComponentAccesibility/">Demo Page from the repository</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Lifecycle Callbacks & Component Communication]]></title><description><![CDATA[Discover Web Components' lifecycle callbacks, communication tips, examples, and a demo app to enhance your skills]]></description><link>https://journeytocode.hashnode.dev/lifecycle-callbacks-and-component-communication</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/lifecycle-callbacks-and-component-communication</guid><category><![CDATA[lifecycle]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[example]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 23 Mar 2025 20:00:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742758862899/7d8139be-7b6f-4e9d-9448-f6a27bb40d89.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this guide, we'll explore how to build truly framework-agnostic UI components that work anywhere—React, Angular, Vue, or even with no framework at all! We'll deep dive into two critical aspects that separate amateur from professional implementations:</p>
<ol>
<li><p><strong>Lifecycle callbacks</strong> - The secret sauce that brings your components to life</p>
</li>
<li><p><strong>Component communication</strong> - Making your components talk to each other seamlessly</p>
</li>
</ol>
<p>Ready to future-proof your front-end skills? Let's dive in!</p>
<hr />
<h2 id="heading-lifecycle-callbacks-the-heartbeat-of-your-components">Lifecycle Callbacks: The Heartbeat of Your Components</h2>
<p>Imagine your Web Component as a living entity with a clear lifecycle: it's born, grows, changes, and eventually leaves the page. Each of these stages triggers specific callback methods that you can tap into:</p>
<h3 id="heading-1-connectedcallback-the-birthday-party">1. <code>connectedCallback()</code>: The Birthday Party</h3>
<p>When your component joins the DOM, it's time to celebrate! This is where initialization happens:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BirthdayComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'🎉 I\'m alive! Time to party!'</span>);
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
      &lt;div class="birthday-card"&gt;
        &lt;h2&gt;Hello World!&lt;/h2&gt;
        &lt;p&gt;I was born at <span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toLocaleTimeString()}</span>&lt;/p&gt;
      &lt;/div&gt;
    `</span>;
    <span class="hljs-comment">// Start any necessary timers, fetch data, etc.</span>
  }
}

customElements.define(<span class="hljs-string">'birthday-card'</span>, BirthdayComponent);
</code></pre>
<p><strong>Try it yourself:</strong> Add <code>&lt;birthday-card&gt;&lt;/birthday-card&gt;</code> to your HTML and watch it come to life in the browser console!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/KwKeGbQ">https://codepen.io/mnichols08/pen/KwKeGbQ</a></div>
<p> </p>
<h3 id="heading-2-disconnectedcallback-the-farewell-tour">2. <code>disconnectedCallback()</code>: The Farewell Tour</h3>
<p>All good things come to an end. When your component is removed from the DOM, use this opportunity for a proper cleanup:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CleanupComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-built_in">this</span>.intervalId = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Still here...'</span>);
    }, <span class="hljs-number">1000</span>);
  }

  disconnectedCallback() {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'💫 It\'s been a pleasure serving you! Cleaning up...'</span>);
    <span class="hljs-built_in">clearInterval</span>(<span class="hljs-built_in">this</span>.intervalId);
    <span class="hljs-comment">// Remove event listeners, close connections, etc.</span>
  }
}

customElements.define(<span class="hljs-string">'cleanup-demo'</span>, CleanupComponent);
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/OPJEaPg">https://codepen.io/mnichols08/pen/OPJEaPg</a></div>
<p> <strong>Hands-on challenge:</strong> Create a component with a button that removes itself after a countdown. Watch the disconnectedCallback fire in your console!</p>
<h3 id="heading-3-attributechangedcallbackname-oldvalue-newvalue-the-chameleon-effect">3. <code>attributeChangedCallback(name, oldValue, newValue)</code>: The Chameleon Effect</h3>
<p>Components need to react to changes. This callback is your component's sense organ:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MoodRing</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() {
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'mood'</span>];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'mood'</span>) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🔄 Mood changing from <span class="hljs-subst">${oldValue || <span class="hljs-string">'unset'</span>}</span> to <span class="hljs-subst">${newValue}</span>!`</span>);

      <span class="hljs-comment">// Update the UI based on the new mood</span>
      <span class="hljs-keyword">const</span> moodEmojis = {
        <span class="hljs-attr">happy</span>: <span class="hljs-string">'😀'</span>,
        <span class="hljs-attr">sad</span>: <span class="hljs-string">'😢'</span>,
        <span class="hljs-attr">angry</span>: <span class="hljs-string">'😠'</span>,
        <span class="hljs-attr">confused</span>: <span class="hljs-string">'🤔'</span>
      };

      <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
        &lt;div class="mood-display"&gt;
          &lt;span style="font-size: 3rem"&gt;<span class="hljs-subst">${moodEmojis[newValue] || <span class="hljs-string">'😐'</span>}</span>&lt;/span&gt;
          &lt;p&gt;I'm feeling <span class="hljs-subst">${newValue || <span class="hljs-string">'neutral'</span>}</span>!&lt;/p&gt;
        &lt;/div&gt;
      `</span>;
    }
  }
}

customElements.define(<span class="hljs-string">'mood-ring'</span>, MoodRing);
</code></pre>
<p><strong>Interactive example:</strong> Click the buttons below to change my mood!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/dPyKQOX">https://codepen.io/mnichols08/pen/dPyKQOX</a></div>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">mood-ring</span> <span class="hljs-attr">mood</span>=<span class="hljs-string">"happy"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">mood-ring</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"document.querySelector('mood-ring').setAttribute('mood', 'happy')"</span>&gt;</span>Happy<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"document.querySelector('mood-ring').setAttribute('mood', 'sad')"</span>&gt;</span>Sad<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"document.querySelector('mood-ring').setAttribute('mood', 'angry')"</span>&gt;</span>Angry<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"document.querySelector('mood-ring').setAttribute('mood', 'confused')"</span>&gt;</span>Confused<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<h3 id="heading-4-adoptedcallback-the-adoption-papers">4. <code>adoptedCallback()</code>: The Adoption Papers</h3>
<p>This one's a bit like finding yourself in a new family. When your element moves to a new document:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AdoptedPet</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  adoptedCallback() {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'🏠 New home! Adjusting to my new document...'</span>);
    <span class="hljs-comment">// Reset state for the new document context</span>
  }
}

customElements.define(<span class="hljs-string">'adopted-pet'</span>, AdoptedPet);
</code></pre>
<p><strong>Fun fact:</strong> While less commonly used, this callback is crucial for applications that work with iframes or multiple documents!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/ogNyQeL">https://codepen.io/mnichols08/pen/ogNyQeL</a></div>
<p> </p>
<h2 id="heading-web-components-in-the-wild">Web Components in the Wild</h2>
<p>Before we dive deeper, let's see who's already embracing this technology:</p>
<ul>
<li><p><strong>Google</strong> uses Web Components across many of their products including YouTube and Google Maps</p>
</li>
<li><p><strong>Microsoft</strong> has embraced them for their Fluent UI design system</p>
</li>
<li><p><strong>Adobe</strong> leverages them in their Spectrum design system</p>
</li>
<li><p><strong>Even Salesforce</strong> built their Lightning Web Components on this technology</p>
</li>
</ul>
<p>Why are these tech giants investing in Web Components? Because they're <strong>future-proof</strong>, <strong>performant</strong>, and <strong>framework-agnostic</strong>!</p>
<hr />
<h2 id="heading-component-communication-lets-talk">Component Communication: Let's Talk!</h2>
<p>Web Components are fantastic at encapsulation, but isolation isn't always desirable. Here's how to make them communicate effectively:</p>
<h3 id="heading-1-parent-child-communication-the-family-chat">1. Parent-Child Communication: The Family Chat</h3>
<p>Just like in human families, parent and child components can have rich communication:</p>
<h4 id="heading-parents-children-passing-down-wisdom">Parents → Children: Passing Down Wisdom</h4>
<p>Parents can pass data to children through attributes or properties:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ParentComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
      &lt;div class="parent"&gt;
        &lt;h2&gt;I'm the parent&lt;/h2&gt;
        &lt;child-component message="Be good!"&gt;&lt;/child-component&gt;
        &lt;button id="change-message"&gt;Change Message&lt;/button&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'#change-message'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> child = <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'child-component'</span>);
      child.setAttribute(<span class="hljs-string">'message'</span>, <span class="hljs-string">'Clean your room!'</span>);
    });
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChildComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() {
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'message'</span>];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'message'</span>) {
      <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'.message'</span>).textContent = newValue;
    }
  }

  connectedCallback() {
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
      &lt;div class="child" style="border: 1px solid blue; padding: 10px; margin: 10px 0;"&gt;
        &lt;h3&gt;I'm the child&lt;/h3&gt;
        &lt;p&gt;Parent says: &lt;span class="message"&gt;<span class="hljs-subst">${<span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'message'</span>) || <span class="hljs-string">'Nothing yet'</span>}</span>&lt;/span&gt;&lt;/p&gt;
        &lt;button class="respond"&gt;Respond to Parent&lt;/button&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'.respond'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'child-response'</span>, {
        <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">detail</span>: { <span class="hljs-attr">response</span>: <span class="hljs-string">'Okay, I will!'</span> }
      }));
    });
  }
}

customElements.define(<span class="hljs-string">'parent-component'</span>, ParentComponent);
customElements.define(<span class="hljs-string">'child-component'</span>, ChildComponent);
</code></pre>
<h4 id="heading-children-parents-speaking-up">Children → Parents: Speaking Up</h4>
<p>Children talk back to parents using custom events:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// In the ParentComponent class</span>
connectedCallback() {
  <span class="hljs-comment">// ... previous code ...</span>

  <span class="hljs-built_in">this</span>.addEventListener(<span class="hljs-string">'child-response'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    alert(<span class="hljs-string">`Child says: <span class="hljs-subst">${e.detail.response}</span>`</span>);
  });
}
</code></pre>
<p><strong>Try it live:</strong> Watch the following demo to see this parent-child communication in action!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/azbKQxZ">https://codepen.io/mnichols08/pen/azbKQxZ</a></div>
<p> </p>
<h3 id="heading-2-sibling-communication-the-playground-talk">2. Sibling Communication: The Playground Talk</h3>
<p>Sometimes components at the same level need to coordinate:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SiblingContainer</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
      &lt;div class="container"&gt;
        &lt;h2&gt;Sibling Communication&lt;/h2&gt;
        &lt;sibling-sender&gt;&lt;/sibling-sender&gt;
        &lt;sibling-receiver&gt;&lt;/sibling-receiver&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-comment">// The parent mediates communication between siblings</span>
    <span class="hljs-built_in">this</span>.addEventListener(<span class="hljs-string">'message-sent'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> receiver = <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'sibling-receiver'</span>);
      receiver.receiveMessage(e.detail.message);
    });
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SiblingSender</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
      &lt;div style="border: 1px solid green; padding: 10px; margin: 10px 0;"&gt;
        &lt;h3&gt;Sibling A (Sender)&lt;/h3&gt;
        &lt;input type="text" placeholder="Type a message" value="Hello sibling!"&gt;
        &lt;button&gt;Send to Sibling B&lt;/button&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'button'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> message = <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'input'</span>).value;
      <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'message-sent'</span>, {
        <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">detail</span>: { message }
      }));
    });
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SiblingReceiver</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
      &lt;div style="border: 1px solid purple; padding: 10px; margin: 10px 0;"&gt;
        &lt;h3&gt;Sibling B (Receiver)&lt;/h3&gt;
        &lt;p&gt;Message received: &lt;span class="message"&gt;Nothing yet&lt;/span&gt;&lt;/p&gt;
      &lt;/div&gt;
    `</span>;
  }

  receiveMessage(message) {
    <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'.message'</span>).textContent = message;
    <span class="hljs-comment">// Add animation to show the message arrived</span>
    <span class="hljs-keyword">const</span> messageElement = <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'.message'</span>);
    messageElement.style.animation = <span class="hljs-string">'highlight 1s'</span>;
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> { messageElement.style.animation = <span class="hljs-string">''</span>; }, <span class="hljs-number">1000</span>);
  }
}

customElements.define(<span class="hljs-string">'sibling-container'</span>, SiblingContainer);
customElements.define(<span class="hljs-string">'sibling-sender'</span>, SiblingSender);
customElements.define(<span class="hljs-string">'sibling-receiver'</span>, SiblingReceiver);
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/vEYrvYd">https://codepen.io/mnichols08/pen/vEYrvYd</a></div>
<h3 id="heading-3-global-event-bus-the-town-square">3. Global Event Bus: The Town Square</h3>
<p>For components that are far apart in the DOM tree, a global event bus works like a town square:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// EventBus.js - The town square where everyone meets</span>
<span class="hljs-keyword">const</span> EventBus = <span class="hljs-keyword">new</span> EventTarget();

<span class="hljs-comment">// Export it so components can import and use it</span>
<span class="hljs-keyword">export</span> { EventBus };
</code></pre>
<p>Using the event bus in components:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { EventBus } <span class="hljs-keyword">from</span> <span class="hljs-string">'./EventBus.js'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalSender</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
      &lt;div style="border: 1px solid red; padding: 10px;"&gt;
        &lt;h3&gt;Global Announcer&lt;/h3&gt;
        &lt;button&gt;Broadcast Announcement&lt;/button&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'button'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
      EventBus.dispatchEvent(<span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'global-announcement'</span>, {
        <span class="hljs-attr">detail</span>: { <span class="hljs-attr">message</span>: <span class="hljs-string">'Attention everyone! Important announcement!'</span> }
      }));
    });
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalListener</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  connectedCallback() {
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`
      &lt;div style="border: 1px solid blue; padding: 10px;"&gt;
        &lt;h3&gt;Global Listener&lt;/h3&gt;
        &lt;p&gt;Last announcement: &lt;span class="announcement"&gt;None yet&lt;/span&gt;&lt;/p&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-comment">// Subscribe to global events</span>
    EventBus.addEventListener(<span class="hljs-string">'global-announcement'</span>, <span class="hljs-built_in">this</span>.handleAnnouncement.bind(<span class="hljs-built_in">this</span>));
  }

  disconnectedCallback() {
    <span class="hljs-comment">// Clean up subscription when element is removed</span>
    EventBus.removeEventListener(<span class="hljs-string">'global-announcement'</span>, <span class="hljs-built_in">this</span>.handleAnnouncement.bind(<span class="hljs-built_in">this</span>));
  }

  handleAnnouncement(event) {
    <span class="hljs-built_in">this</span>.querySelector(<span class="hljs-string">'.announcement'</span>).textContent = event.detail.message;
  }
}

customElements.define(<span class="hljs-string">'global-sender'</span>, GlobalSender);
customElements.define(<span class="hljs-string">'global-listener'</span>, GlobalListener);
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/NPWzeqm">https://codepen.io/mnichols08/pen/NPWzeqm</a></div>
<p> <strong>Pro tip:</strong> This pattern works great for notifications, theme changes, or user authentication status that many components need to know about!</p>
<hr />
<p>Each action demonstrates a different aspect of Web Components communication!</p>
<p>Check out the live demo below, and explore the code to see how it implements the concepts we've learned.</p>
<h2 id="heading-common-pitfalls-to-avoid">Common Pitfalls to Avoid</h2>
<ol>
<li><p><strong>Forgetting to clean up resources</strong> in <code>disconnectedCallback()</code> - This is the #1 cause of memory leaks!</p>
</li>
<li><p><strong>Not using Shadow DOM</strong> when appropriate - Without it, your CSS might leak out or be affected by the parent page</p>
</li>
<li><p><strong>Overusing attributes</strong> for complex data - Consider properties instead for objects and arrays</p>
</li>
<li><p><strong>Ignoring browser compatibility</strong> - Always check <a target="_blank" href="https://caniuse.com/#feat=custom-elementsv1">Can I Use</a> and consider polyfills for older browsers</p>
</li>
<li><p><strong>Creating too many small components</strong> - Every component has overhead; find the right balance for your application</p>
</li>
</ol>
<h2 id="heading-expert-tips">Expert Tips</h2>
<ul>
<li><p><strong>Debug attribute changes</strong> by adding a console.log in attributeChangedCallback</p>
</li>
<li><p><strong>Create a base component class</strong> that handles common functionality for all your components</p>
</li>
<li><p><strong>Document your components</strong> with clear API comments - your future self will thank you!</p>
</li>
<li><p><strong>Use Shadow DOM</strong> for true encapsulation by adding <code>this.attachShadow({mode: 'open'})</code> in your constructor</p>
</li>
<li><p><strong>Leverage custom events</strong> for component communication rather than directly accessing other components</p>
</li>
</ul>
<h2 id="heading-try-it-yourself-interactive-playground">Try It Yourself: Interactive Playground</h2>
<p>Want to experiment without setting up a project? Copy and paste this code into <a target="_blank" href="https://codepen.io">CodePen</a> or <a target="_blank" href="https://jsfiddle.net">JSFiddle</a> to see Web Components in action immediately:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ColorToggler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.colors = [<span class="hljs-string">'#e91e63'</span>, <span class="hljs-string">'#2196f3'</span>, <span class="hljs-string">'#4caf50'</span>, <span class="hljs-string">'#ff9800'</span>];
    <span class="hljs-built_in">this</span>.currentIndex = <span class="hljs-number">0</span>;
  }

  connectedCallback() {
    <span class="hljs-built_in">this</span>.style.display = <span class="hljs-string">'block'</span>;
    <span class="hljs-built_in">this</span>.style.padding = <span class="hljs-string">'20px'</span>;
    <span class="hljs-built_in">this</span>.style.margin = <span class="hljs-string">'20px'</span>;
    <span class="hljs-built_in">this</span>.style.backgroundColor = <span class="hljs-built_in">this</span>.colors[<span class="hljs-number">0</span>];
    <span class="hljs-built_in">this</span>.style.color = <span class="hljs-string">'white'</span>;
    <span class="hljs-built_in">this</span>.style.borderRadius = <span class="hljs-string">'8px'</span>;
    <span class="hljs-built_in">this</span>.style.cursor = <span class="hljs-string">'pointer'</span>;
    <span class="hljs-built_in">this</span>.style.transition = <span class="hljs-string">'background-color 0.3s'</span>;
    <span class="hljs-built_in">this</span>.style.userSelect = <span class="hljs-string">'none'</span>;

    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">'&lt;h3&gt;Click me to change color!&lt;/h3&gt;'</span>;
    <span class="hljs-built_in">this</span>.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-built_in">this</span>.cycleColor.bind(<span class="hljs-built_in">this</span>));
  }

  cycleColor() {
    <span class="hljs-built_in">this</span>.currentIndex = (<span class="hljs-built_in">this</span>.currentIndex + <span class="hljs-number">1</span>) % <span class="hljs-built_in">this</span>.colors.length;
    <span class="hljs-built_in">this</span>.style.backgroundColor = <span class="hljs-built_in">this</span>.colors[<span class="hljs-built_in">this</span>.currentIndex];
  }

  disconnectedCallback() {
    <span class="hljs-built_in">this</span>.removeEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-built_in">this</span>.cycleColor);
  }
}

customElements.define(<span class="hljs-string">'color-toggler'</span>, ColorToggler);

<span class="hljs-comment">// Add to your HTML: &lt;color-toggler&gt;&lt;/color-toggler&gt;</span>
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/wBvXRKN">https://codepen.io/mnichols08/pen/wBvXRKN</a></div>
<p> <strong>Challenge:</strong> Can you modify the component to display the current color name when clicked? (Solution at the bottom of this article)</p>
<hr />
<p><strong>Challenge Solution</strong></p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ColorToggler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.colors = [
      { <span class="hljs-attr">hex</span>: <span class="hljs-string">'#e91e63'</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'Pink'</span> },
      { <span class="hljs-attr">hex</span>: <span class="hljs-string">'#2196f3'</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'Blue'</span> },
      { <span class="hljs-attr">hex</span>: <span class="hljs-string">'#4caf50'</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'Green'</span> },
      { <span class="hljs-attr">hex</span>: <span class="hljs-string">'#ff9800'</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'Orange'</span> }
    ];
    <span class="hljs-built_in">this</span>.currentIndex = <span class="hljs-number">0</span>;
  }

  connectedCallback() {
    <span class="hljs-built_in">this</span>.style.display = <span class="hljs-string">'block'</span>;
    <span class="hljs-built_in">this</span>.style.padding = <span class="hljs-string">'20px'</span>;
    <span class="hljs-built_in">this</span>.style.margin = <span class="hljs-string">'20px'</span>;
    <span class="hljs-built_in">this</span>.style.backgroundColor = <span class="hljs-built_in">this</span>.colors[<span class="hljs-number">0</span>].hex;
    <span class="hljs-built_in">this</span>.style.color = <span class="hljs-string">'white'</span>;
    <span class="hljs-built_in">this</span>.style.borderRadius = <span class="hljs-string">'8px'</span>;
    <span class="hljs-built_in">this</span>.style.cursor = <span class="hljs-string">'pointer'</span>;
    <span class="hljs-built_in">this</span>.style.transition = <span class="hljs-string">'background-color 0.3s'</span>;
    <span class="hljs-built_in">this</span>.style.userSelect = <span class="hljs-string">'none'</span>;    
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`&lt;h3&gt;Click me! (Current: <span class="hljs-subst">${<span class="hljs-built_in">this</span>.colors[<span class="hljs-number">0</span>].name}</span>)&lt;/h3&gt;`</span>;
    <span class="hljs-built_in">this</span>.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-built_in">this</span>.cycleColor.bind(<span class="hljs-built_in">this</span>));
  }

  cycleColor() {
    <span class="hljs-built_in">this</span>.currentIndex = (<span class="hljs-built_in">this</span>.currentIndex + <span class="hljs-number">1</span>) % <span class="hljs-built_in">this</span>.colors.length;
    <span class="hljs-keyword">const</span> currentColor = <span class="hljs-built_in">this</span>.colors[<span class="hljs-built_in">this</span>.currentIndex];
    <span class="hljs-built_in">this</span>.style.backgroundColor = currentColor.hex;
    <span class="hljs-built_in">this</span>.innerHTML = <span class="hljs-string">`&lt;h3&gt;Click me! (Current: <span class="hljs-subst">${currentColor.name}</span>)&lt;/h3&gt;`</span>;
  }

      disconnectedCallback() {
    <span class="hljs-built_in">this</span>.removeEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-built_in">this</span>.cycleColor);
  }
}

customElements.define(<span class="hljs-string">'color-toggler'</span>, ColorToggler);
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/LEYrMZz">https://codepen.io/mnichols08/pen/LEYrMZz</a></div>
<p> </p>
<hr />
<h2 id="heading-additional-resources">Additional Resources:</h2>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">MDN Web Components Guide</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/mnichols08/WebComponentCallbacks">GitHub Repo for this Demo</a></p>
</li>
<li><p><a target="_blank" href="https://mnichols08.github.io/WebComponentCallbacks/">Demo of Web Components Discussed Here</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Mastering the Art of Web Component Styling]]></title><description><![CDATA[Advanced Web Component styling: Shadow DOM, performance optimization, dynamic styles, and design system integration for cohesive, scalable libraries]]></description><link>https://journeytocode.hashnode.dev/mastering-the-art-of-web-component-styling</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/mastering-the-art-of-web-component-styling</guid><category><![CDATA[styling css]]></category><category><![CDATA[CSS]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[styled-components]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[learning]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 16 Mar 2025 15:57:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742140526954/9944aed7-0074-4070-a7b9-f2c4d6bbe249.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The advent of Web Components marked a paradigm shift in front-end development, introducing a standardized way to create encapsulated, reusable custom elements. At the core of this architectural pattern lies a sophisticated approach to styling—one that demands a nuanced understanding of DOM isolation, inheritance chains, and CSS optimization techniques.</p>
<p>This guide explores the technical foundations and advanced implementation strategies for styling Web Components in production environments, with a focus on performance, maintainability, and design system integration.</p>
<h2 id="heading-shadow-dom-the-technical-foundation-of-style-encapsulation">Shadow DOM: The Technical Foundation of Style Encapsulation</h2>
<p>The Shadow DOM provides a powerful isolation mechanism that creates a separate DOM tree with its own scoped styling context. This architectural approach solves one of the most persistent challenges in front-end development: CSS collision and specificity conflicts.</p>
<p>When implementing Shadow DOM encapsulation, you're effectively creating a styling boundary with controlled entry and exit points:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AdvancedComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-comment">// Create an isolated DOM tree with its own styling context</span>
    <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ 
      <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span>,
      <span class="hljs-comment">// Optional: Control which styles can pierce the shadow boundary</span>
      <span class="hljs-attr">delegatesFocus</span>: <span class="hljs-literal">true</span> 
    });

    shadow.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        :host {
          display: block;
          contain: content; /* CSS containment for performance */
        }
        .container {
          background-color: var(--bg-primary, #f0f4f8);
          padding: var(--spacing-md, 16px);
          border-radius: var(--border-radius-md, 4px);
          box-shadow: var(--shadow-sm, 0 1px 3px rgba(0,0,0,0.1));
          transition: all 180ms ease-out;
        }
        ::slotted(*) {
          margin-bottom: var(--spacing-sm, 8px);
        }
      &lt;/style&gt;
      &lt;div class="container"&gt;
        &lt;slot&gt;&lt;/slot&gt;
      &lt;/div&gt;
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'advanced-component'</span>, AdvancedComponent);
</code></pre>
<p>This implementation showcases several critical technical patterns:</p>
<ul>
<li><p>The <code>:host</code> selector targets the custom element itself</p>
</li>
<li><p>CSS containment optimizes rendering performance</p>
</li>
<li><p>CSS custom properties create a styling API</p>
</li>
<li><p>The <code>::slotted</code> pseudo-element styles projected content</p>
</li>
</ul>
<p>Seeing is believing when it comes to understanding the true power of Shadow DOM encapsulation. In this interactive example, you can observe how Shadow DOM completely isolates component styles from the surrounding page. Notice how the global styles (with red borders and yellow backgrounds) affect the regular DOM element but have no effect on the Shadow DOM components. Try toggling between light and dark mode to see how components adapt, and inspect the difference between open and closed Shadow DOM modes.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/WbbNbKa">https://codepen.io/mnichols08/pen/WbbNbKa</a></div>
<p> </p>
<h3 id="heading-understanding-shadow-dom-modes-open-vs-closed">Understanding Shadow DOM Modes: Open vs. Closed</h3>
<p>When attaching a shadow root to a component, the <code>mode</code> property defines a critical aspect of your component's encapsulation model. This choice directly impacts both styling and JavaScript interaction capabilities:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Open mode: External JavaScript can access the shadow DOM</span>
<span class="hljs-keyword">const</span> openShadow = element.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });

<span class="hljs-comment">// Closed mode: External JavaScript cannot directly access the shadow DOM</span>
<span class="hljs-keyword">const</span> closedShadow = element.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'closed'</span> });
</code></pre>
<h4 id="heading-when-to-use-mode-closed">When to Use <code>mode: 'closed'</code></h4>
<p>The <code>closed</code> mode creates a stricter boundary around your component, preventing external JavaScript from accessing its shadow DOM through the <code>.shadowRoot</code> property. While <code>open</code> mode is more common for most use cases, <code>closed</code> mode serves specific technical purposes:</p>
<ol>
<li><p><strong>Security-Sensitive Components</strong>: When building components that process sensitive data (payment forms, authentication interfaces)</p>
</li>
<li><p><strong>Preventing DOM Manipulation</strong>: To stop external scripts from modifying your component's internal structure</p>
</li>
<li><p><strong>Strict API Enforcement</strong>: Ensuring that all interactions happen through your explicitly defined APIs</p>
</li>
<li><p><strong>Third-Party Protection</strong>: Protecting your component from interference by third-party scripts on the page</p>
</li>
<li><p><strong>Browser-Like Components</strong>: When emulating native browser elements that use closed shadow roots</p>
</li>
</ol>
<p>Here's an implementation example of a component with a closed shadow root:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SecureComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();

    <span class="hljs-comment">// Create a closed shadow root</span>
    <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'closed'</span> });

    <span class="hljs-comment">// Store internal state and references privately using closure</span>
    <span class="hljs-keyword">const</span> state = {
      <span class="hljs-attr">value</span>: <span class="hljs-string">''</span>,
      <span class="hljs-attr">isValid</span>: <span class="hljs-literal">false</span>
    };

    <span class="hljs-comment">// Create internal DOM structure</span>
    <span class="hljs-keyword">const</span> input = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'input'</span>);
    input.type = <span class="hljs-string">'password'</span>;
    input.setAttribute(<span class="hljs-string">'placeholder'</span>, <span class="hljs-string">'Enter secure data'</span>);

    <span class="hljs-keyword">const</span> statusIndicator = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
    statusIndicator.className = <span class="hljs-string">'status-indicator'</span>;

    <span class="hljs-comment">// Add styles with maximum encapsulation</span>
    <span class="hljs-keyword">const</span> style = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'style'</span>);
    style.textContent = <span class="hljs-string">`
      :host {
        display: block;
        border: 1px solid #ddd;
        border-radius: 4px;
        padding: 16px;
        background: #f9f9f9;
      }

      input {
        border: 1px solid #ccc;
        padding: 8px;
        border-radius: 4px;
        width: 100%;
        box-sizing: border-box;
      }

      .status-indicator {
        height: 8px;
        margin-top: 8px;
        border-radius: 4px;
        background-color: #ccc;
        transition: background-color 0.3s ease;
      }

      .status-indicator.valid {
        background-color: #4caf50;
      }

      .status-indicator.invalid {
        background-color: #f44336;
      }
    `</span>;

    <span class="hljs-comment">// Add event listeners that maintain encapsulation</span>
    input.addEventListener(<span class="hljs-string">'input'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
      state.value = e.target.value;
      <span class="hljs-built_in">this</span>._validateAndUpdate(state, statusIndicator);

      <span class="hljs-comment">// Communicate changes through events rather than direct access</span>
      <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'secure-input-change'</span>, {
        <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">composed</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">detail</span>: { <span class="hljs-attr">isValid</span>: state.isValid }
      }));
    });

    <span class="hljs-comment">// Add elements to shadow DOM</span>
    shadow.appendChild(style);
    shadow.appendChild(input);
    shadow.appendChild(statusIndicator);

    <span class="hljs-comment">// Expose methods through the element itself, not through shadowRoot</span>
    <span class="hljs-built_in">this</span>.reset = <span class="hljs-function">() =&gt;</span> {
      state.value = <span class="hljs-string">''</span>;
      state.isValid = <span class="hljs-literal">false</span>;
      input.value = <span class="hljs-string">''</span>;
      statusIndicator.className = <span class="hljs-string">'status-indicator'</span>;
    };

    <span class="hljs-built_in">this</span>.getValue = <span class="hljs-function">() =&gt;</span> state.isValid ? state.value : <span class="hljs-literal">null</span>;
  }

  <span class="hljs-comment">// Private method (uses closure to access internal elements)</span>
  _validateAndUpdate(state, indicator) {
    <span class="hljs-comment">// Simple validation logic</span>
    state.isValid = state.value.length &gt;= <span class="hljs-number">8</span>;

    <span class="hljs-comment">// Update UI to reflect state</span>
    indicator.className = state.isValid 
      ? <span class="hljs-string">'status-indicator valid'</span> 
      : <span class="hljs-string">'status-indicator invalid'</span>;
  }

  <span class="hljs-comment">// Lifecycle methods still work normally</span>
  connectedCallback() {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Secure component connected'</span>);
  }

  disconnectedCallback() {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Secure component disconnected'</span>);
  }
}

customElements.define(<span class="hljs-string">'secure-component'</span>, SecureComponent);
</code></pre>
<p>This secure component demonstrates key principles of closed shadow DOM usage:</p>
<ol>
<li><p><strong>Private State Management</strong>: State is maintained in closure, inaccessible to external scripts</p>
</li>
<li><p><strong>Explicit API Design</strong>: Functionality is exposed through carefully designed methods on the element</p>
</li>
<li><p><strong>Event-Based Communication</strong>: Changes are communicated through custom events</p>
</li>
<li><p><strong>Style Encapsulation</strong>: Styles are fully contained and inaccessible externally</p>
</li>
</ol>
<h4 id="heading-technical-considerations-and-tradeoffs">Technical Considerations and Tradeoffs</h4>
<p>While <code>mode: 'closed'</code> offers stronger encapsulation, it comes with significant tradeoffs:</p>
<ol>
<li><p><strong>Debugging Challenges</strong>: Inspecting and debugging closed shadow DOMs is more difficult</p>
</li>
<li><p><strong>Testing Complexity</strong>: Unit tests can't easily access internal elements</p>
</li>
<li><p><strong>External Styling Limitations</strong>: Makes providing styling hooks slightly more complex</p>
</li>
<li><p><strong>Third-Party Extensions</strong>: Prevents legitimate extensions or enhancements</p>
</li>
<li><p><strong>Developer Experience</strong>: Can create friction for legitimate component users</p>
</li>
</ol>
<p>Most component libraries choose <code>mode: 'open'</code> for these reasons, as it provides sufficient encapsulation while maintaining better developer experience. Reserve <code>mode: 'closed'</code> for cases where the enhanced security and encapsulation are explicitly required by the component's purpose.</p>
<p><strong>Note on Lifecycle Implications</strong>: While shadow DOM mode doesn't directly affect component lifecycle methods like <code>connectedCallback</code> or <code>disconnectedCallback</code>, it does influence how those methods interact with the component's internal DOM. With closed mode, lifecycle methods need to rely on closure variables or instance properties to manipulate the shadow DOM, as we'll explore further in our next article on component lifecycle management.</p>
<h2 id="heading-advanced-styling-architectures">Advanced Styling Architectures</h2>
<h3 id="heading-1-dynamic-style-injection-with-performance-optimization">1. Dynamic Style Injection with Performance Optimization</h3>
<p>For components requiring dynamic styling based on state or props, a more sophisticated approach involves programmatically generating and injecting styles with performance considerations:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DynamicStyledComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() {
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'variant'</span>, <span class="hljs-string">'size'</span>, <span class="hljs-string">'disabled'</span>];
  }

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-built_in">this</span>._styleElement = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'style'</span>);
    <span class="hljs-built_in">this</span>.shadow.appendChild(<span class="hljs-built_in">this</span>._styleElement);

    <span class="hljs-comment">// Create the component structure</span>
    <span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
    container.className = <span class="hljs-string">'container'</span>;
    container.innerHTML = <span class="hljs-string">'&lt;slot&gt;&lt;/slot&gt;'</span>;
    <span class="hljs-built_in">this</span>.shadow.appendChild(container);

    <span class="hljs-comment">// Initial render</span>
    <span class="hljs-built_in">this</span>._updateStyles();
  }

  attributeChangedCallback() {
    <span class="hljs-comment">// Debounced style updates for performance</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>._styleUpdateScheduled) <span class="hljs-keyword">return</span>;
    <span class="hljs-built_in">this</span>._styleUpdateScheduled = <span class="hljs-literal">true</span>;

    requestAnimationFrame(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>._updateStyles();
      <span class="hljs-built_in">this</span>._styleUpdateScheduled = <span class="hljs-literal">false</span>;
    });
  }

  _updateStyles() {
    <span class="hljs-keyword">const</span> variant = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'variant'</span>) || <span class="hljs-string">'default'</span>;
    <span class="hljs-keyword">const</span> size = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'size'</span>) || <span class="hljs-string">'medium'</span>;
    <span class="hljs-keyword">const</span> disabled = <span class="hljs-built_in">this</span>.hasAttribute(<span class="hljs-string">'disabled'</span>);

    <span class="hljs-keyword">const</span> variantStyles = {
      <span class="hljs-attr">default</span>: {
        <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">'var(--color-surface, white)'</span>,
        <span class="hljs-attr">color</span>: <span class="hljs-string">'var(--color-text, #333)'</span>
      },
      <span class="hljs-attr">primary</span>: {
        <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">'var(--color-primary, #0070f3)'</span>,
        <span class="hljs-attr">color</span>: <span class="hljs-string">'var(--color-primary-contrast, white)'</span>
      },
      <span class="hljs-comment">// Additional variants</span>
    };

    <span class="hljs-keyword">const</span> sizeStyles = {
      <span class="hljs-attr">small</span>: { <span class="hljs-attr">padding</span>: <span class="hljs-string">'8px 12px'</span>, <span class="hljs-attr">fontSize</span>: <span class="hljs-string">'14px'</span> },
      <span class="hljs-attr">medium</span>: { <span class="hljs-attr">padding</span>: <span class="hljs-string">'12px 16px'</span>, <span class="hljs-attr">fontSize</span>: <span class="hljs-string">'16px'</span> },
      <span class="hljs-attr">large</span>: { <span class="hljs-attr">padding</span>: <span class="hljs-string">'16px 24px'</span>, <span class="hljs-attr">fontSize</span>: <span class="hljs-string">'18px'</span> }
    };

    <span class="hljs-keyword">const</span> selectedVariant = variantStyles[variant] || variantStyles.default;
    <span class="hljs-keyword">const</span> selectedSize = sizeStyles[size] || sizeStyles.medium;

    <span class="hljs-comment">// Generate optimized CSS</span>
    <span class="hljs-built_in">this</span>._styleElement.textContent = <span class="hljs-string">`
      .container {
        <span class="hljs-subst">${<span class="hljs-built_in">Object</span>.entries(selectedVariant).map(([prop, value]) =&gt; <span class="hljs-string">`<span class="hljs-subst">${prop}</span>: <span class="hljs-subst">${value}</span>;`</span>).join(<span class="hljs-string">'\n        '</span>)}</span>
        <span class="hljs-subst">${<span class="hljs-built_in">Object</span>.entries(selectedSize).map(([prop, value]) =&gt; <span class="hljs-string">`<span class="hljs-subst">${prop}</span>: <span class="hljs-subst">${prop}</span>: <span class="hljs-subst">${value}</span>;`</span>).join(<span class="hljs-string">'\n        '</span>)}</span>
        <span class="hljs-subst">${disabled ? <span class="hljs-string">'opacity: 0.6; pointer-events: none;'</span> : <span class="hljs-string">''</span>}</span>
        border-radius: var(--border-radius, 4px);
        transition: box-shadow 150ms ease-in-out;
      }

      .container:hover {
        <span class="hljs-subst">${disabled ? <span class="hljs-string">''</span> : <span class="hljs-string">'box-shadow: var(--shadow-hover, 0 4px 8px rgba(0,0,0,0.1));'</span>}</span>
      }
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'dynamic-styled-component'</span>, DynamicStyledComponent);
</code></pre>
<p>This implementation demonstrates several advanced techniques:</p>
<ul>
<li><p>Attribute-driven styling with observed attributes</p>
</li>
<li><p>Performance optimization via RAF debouncing</p>
</li>
<li><p>Dynamic style generation based on component state</p>
</li>
<li><p>Conditional style application</p>
</li>
</ul>
<p>This demonstration brings to life the dynamic styling patterns we've just explored. Experiment with different variants, sizes, and states to see how attribute-driven styling transforms the component in real-time. The implementation uses requestAnimationFrame for performance optimization and demonstrates how CSS custom properties create a flexible styling API while maintaining visual consistency. Try toggling between light and dark modes to see how the component adapts to different color schemes.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/MYYWYBM">https://codepen.io/mnichols08/pen/MYYWYBM</a></div>
<p> </p>
<h3 id="heading-2-constructable-stylesheets-for-runtime-optimization">2. Constructable Stylesheets for Runtime Optimization</h3>
<p>Constructable Stylesheets represent the pinnacle of styling performance for Web Components, offering shared stylesheet objects that minimize memory consumption and optimize rendering paths:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Create module-scoped stylesheets</span>
<span class="hljs-keyword">const</span> baseStyles = <span class="hljs-keyword">new</span> CSSStyleSheet();
baseStyles.replaceSync(<span class="hljs-string">`
  :host {
    display: block;
    font-family: var(--font-family, system-ui);
  }
  .base {
    box-sizing: border-box;
    width: 100%;
  }
`</span>);

<span class="hljs-keyword">const</span> themeStyles = <span class="hljs-keyword">new</span> CSSStyleSheet();
themeStyles.replaceSync(<span class="hljs-string">`
  .themed {
    color: var(--text-color, #212121);
    background: var(--background-color, #ffffff);
  }
`</span>);

<span class="hljs-comment">// Component implementation with shared stylesheets</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OptimizedComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });

    <span class="hljs-comment">// Adopt shared stylesheets (no duplication in memory)</span>
    shadow.adoptedStyleSheets = [baseStyles, themeStyles];

    <span class="hljs-comment">// Create component structure</span>
    shadow.innerHTML = <span class="hljs-string">`
      &lt;div class="base themed"&gt;
        &lt;slot&gt;&lt;/slot&gt;
      &lt;/div&gt;
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'optimized-component'</span>, OptimizedComponent);
</code></pre>
<p>The technical advantages of this approach include:</p>
<ul>
<li><p>Sheet objects are parsed once but used by multiple component instances</p>
</li>
<li><p>Memory usage is significantly reduced compared to duplicated inline styles</p>
</li>
<li><p>Browser rendering optimizations can be applied to shared stylesheets</p>
</li>
<li><p>Updates to shared stylesheets automatically propagate to all components</p>
</li>
</ul>
<h2 id="heading-creating-a-component-design-system-with-css-custom-properties">Creating a Component Design System with CSS Custom Properties</h2>
<p>To implement a cohesive design system through Web Components, a systematic approach to CSS custom properties creates a powerful styling API:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// design-system.js</span>
<span class="hljs-keyword">const</span> designSystem = {
  <span class="hljs-comment">// Define all design tokens at the :root level</span>
  installGlobalTokens() {
    <span class="hljs-keyword">const</span> tokens = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'style'</span>);
    tokens.textContent = <span class="hljs-string">`
      :root {
        /* Color system */
        --color-primary-50: #e3f2fd;
        --color-primary-100: #bbdefb;
        --color-primary-500: #2196f3;
        --color-primary-900: #0d47a1;

        /* Typography */
        --font-size-xs: 12px;
        --font-size-sm: 14px;
        --font-size-md: 16px;
        --font-size-lg: 18px;
        --font-size-xl: 20px;

        /* Spacing system */
        --spacing-unit: 4px;
        --spacing-xs: calc(var(--spacing-unit) * 1);
        --spacing-sm: calc(var(--spacing-unit) * 2);
        --spacing-md: calc(var(--spacing-unit) * 4);
        --spacing-lg: calc(var(--spacing-unit) * 6);
        --spacing-xl: calc(var(--spacing-unit) * 10);

        /* Elevation */
        --shadow-1: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
        --shadow-2: 0 3px 6px rgba(0,0,0,0.15), 0 2px 4px rgba(0,0,0,0.12);
        --shadow-3: 0 10px 20px rgba(0,0,0,0.15), 0 3px 6px rgba(0,0,0,0.10);

        /* Border radius */
        --radius-sm: 2px;
        --radius-md: 4px;
        --radius-lg: 8px;
        --radius-full: 999px;

        /* Animation */
        --duration-fast: 150ms;
        --duration-normal: 300ms;
        --duration-slow: 500ms;
        --easing-standard: cubic-bezier(0.4, 0.0, 0.2, 1);
      }
    `</span>;
    <span class="hljs-built_in">document</span>.head.appendChild(tokens);
  },

  <span class="hljs-comment">// Component tokens derived from global tokens</span>
  getComponentTokens(name) {
    <span class="hljs-keyword">const</span> style = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'style'</span>);

    <span class="hljs-comment">// Component-specific custom properties derived from global tokens</span>
    <span class="hljs-keyword">const</span> componentTokens = {
      <span class="hljs-string">'ds-button'</span>: <span class="hljs-string">`
        --button-bg: var(--color-primary-500);
        --button-color: white;
        --button-padding: var(--spacing-sm) var(--spacing-md);
        --button-radius: var(--radius-md);
        --button-shadow: var(--shadow-1);
      `</span>,
      <span class="hljs-string">'ds-card'</span>: <span class="hljs-string">`
        --card-bg: white;
        --card-border: 1px solid rgba(0,0,0,0.1);
        --card-padding: var(--spacing-md);
        --card-radius: var(--radius-md);
        --card-shadow: var(--shadow-1);
      `</span>
    };

    style.textContent = <span class="hljs-string">`
      <span class="hljs-subst">${name}</span> {
        <span class="hljs-subst">${componentTokens[name] || <span class="hljs-string">''</span>}</span>
      }
    `</span>;

    <span class="hljs-keyword">return</span> style;
  }
};

<span class="hljs-comment">// Install design system tokens</span>
<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, <span class="hljs-function">() =&gt;</span> {
  designSystem.installGlobalTokens();
});
</code></pre>
<p>This approach enables several advanced styling capabilities:</p>
<ul>
<li><p>Centralized design token management</p>
</li>
<li><p>Hierarchical theming with cascading defaults</p>
</li>
<li><p>Component-specific styling APIs that respect the global design system</p>
</li>
<li><p>Runtime theming capabilities without component modification</p>
</li>
</ul>
<p>Experience the power of a comprehensive design system built with CSS custom properties. This interactive example showcases how design tokens cascade through your component hierarchy, creating a consistent visual language. The design system automatically adapts between light and dark modes while maintaining component-specific styling APIs. Explore the color tokens, typography scale, and spacing system that form the foundation of the design system, then see how they're applied to create cohesive components that respond to their environment.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/RNNwNYL">https://codepen.io/mnichols08/pen/RNNwNYL</a></div>
<p> </p>
<h2 id="heading-inheritance-and-context-adaptation-techniques">Inheritance and Context Adaptation Techniques</h2>
<p>For components that need to adapt to their environment while maintaining encapsulation, sophisticated context-handling techniques can be employed:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ContextAwareComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });

    shadow.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        /* Base responsive container that adapts to context */
        :host {
          display: block;
          /* Inherit typography properties purposefully */
          font-family: inherit;
          font-size: inherit;
          line-height: inherit;
          color: inherit;
          /* Default containment for performance */
          contain: content;
        }

        /* Contextual adaptation */
        :host([data-context="card"]) .content {
          padding: var(--card-inner-spacing, 16px);
        }

        :host([data-context="sidebar"]) .content {
          padding: var(--sidebar-item-spacing, 8px 12px);
        }

        /* Content styles */
        .content {
          background-color: var(--component-bg, transparent);
          border-radius: var(--component-radius, 4px);
          transition: background-color var(--duration-normal, 300ms) ease;
        }

        /* Advanced slotted content styling */
        ::slotted(h1),
        ::slotted(h2),
        ::slotted(h3) {
          margin-top: 0;
          color: var(--heading-color, currentColor);
        }

        ::slotted(p) {
          margin-bottom: var(--paragraph-spacing, 1em);
        }
      &lt;/style&gt;
      &lt;div class="content"&gt;
        &lt;slot&gt;&lt;/slot&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-comment">// Context detection for environment-specific styling</span>
    <span class="hljs-built_in">this</span>._detectContext();

    <span class="hljs-comment">// MutationObserver for dynamic context changes</span>
    <span class="hljs-built_in">this</span>._observer = <span class="hljs-keyword">new</span> MutationObserver(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>._detectContext());
    <span class="hljs-built_in">this</span>._observer.observe(<span class="hljs-built_in">this</span>, { <span class="hljs-attr">attributes</span>: <span class="hljs-literal">true</span> });
  }

  connectedCallback() {
    <span class="hljs-comment">// Re-evaluate context when the component is inserted into a new location</span>
    <span class="hljs-built_in">this</span>._detectContext();
    <span class="hljs-built_in">this</span>._startContextObservation();
  }

  disconnectedCallback() {
    <span class="hljs-built_in">this</span>._observer.disconnect();
  }

  _startContextObservation() {
    <span class="hljs-comment">// Walk up the DOM tree to find parent components that might change</span>
    <span class="hljs-keyword">let</span> parent = <span class="hljs-built_in">this</span>.parentElement;
    <span class="hljs-keyword">while</span> (parent) {
      <span class="hljs-keyword">if</span> (parent.tagName?.includes(<span class="hljs-string">'-'</span>)) {
        <span class="hljs-built_in">this</span>._observer.observe(parent, { <span class="hljs-attr">attributes</span>: <span class="hljs-literal">true</span> });
      }
      parent = parent.parentElement;
    }
  }

  _detectContext() {
    <span class="hljs-comment">// Detect where this component is being used</span>
    <span class="hljs-keyword">const</span> parentCard = <span class="hljs-built_in">this</span>.closest(<span class="hljs-string">'card-component'</span>);
    <span class="hljs-keyword">const</span> parentSidebar = <span class="hljs-built_in">this</span>.closest(<span class="hljs-string">'sidebar-component'</span>);

    <span class="hljs-keyword">if</span> (parentCard) {
      <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">'data-context'</span>, <span class="hljs-string">'card'</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (parentSidebar) {
      <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">'data-context'</span>, <span class="hljs-string">'sidebar'</span>);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">this</span>.removeAttribute(<span class="hljs-string">'data-context'</span>);
    }
  }
}

customElements.define(<span class="hljs-string">'context-aware-component'</span>, ContextAwareComponent);
</code></pre>
<p>This implementation demonstrates advanced environment adaptation techniques:</p>
<ul>
<li><p>Selective inheritance of typography properties</p>
</li>
<li><p>Context detection through DOM traversal</p>
</li>
<li><p>Dynamic adaptation through attribute-based styling</p>
</li>
<li><p>MutationObserver for responding to contextual changes</p>
</li>
</ul>
<p>This demonstration reveals how sophisticated components can intelligently adapt to their surrounding context. The same component automatically adjusts its appearance when placed in different containers (cards vs. sidebars) and responds to theme changes without requiring manual style updates. The implementation uses MutationObserver to detect DOM changes and dynamically updates styling based on the component's environment. Try toggling between container types to see how the component repositions and restyling itself, and switch between light and dark modes to observe theme adaptation in action.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/PwwowdR">https://codepen.io/mnichols08/pen/PwwowdR</a></div>
<p> </p>
<h2 id="heading-performance-optimization-strategies">Performance Optimization Strategies</h2>
<p>Optimal Web Component styling requires consideration of rendering performance, particularly when designing systems that may include hundreds of component instances.</p>
<h3 id="heading-critical-rendering-path-optimization">Critical Rendering Path Optimization</h3>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PerformanceOptimizedComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });

    <span class="hljs-comment">// Prioritize structural rendering</span>
    <span class="hljs-built_in">this</span>._createDOMStructure();

    <span class="hljs-comment">// Defer non-critical styles</span>
    <span class="hljs-built_in">this</span>._deferNonCriticalStyles();
  }

  _createDOMStructure() {
    <span class="hljs-comment">// Critical CSS for initial render</span>
    <span class="hljs-keyword">const</span> criticalStyle = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'style'</span>);
    criticalStyle.textContent = <span class="hljs-string">`
      :host { display: block; }
      .container { min-height: 20px; }
    `</span>;

    <span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
    container.className = <span class="hljs-string">'container'</span>;
    container.innerHTML = <span class="hljs-string">'&lt;slot&gt;&lt;/slot&gt;'</span>;

    <span class="hljs-built_in">this</span>.shadow.appendChild(criticalStyle);
    <span class="hljs-built_in">this</span>.shadow.appendChild(container);
  }

  _deferNonCriticalStyles() {
    <span class="hljs-comment">// Use requestIdleCallback for non-critical styling</span>
    requestIdleCallback(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> enhancedStyle = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'style'</span>);
      enhancedStyle.textContent = <span class="hljs-string">`
        .container {
          background-color: var(--bg-color, #f9f9f9);
          border-radius: var(--radius, 4px);
          box-shadow: var(--shadow, 0 2px 4px rgba(0,0,0,0.05));
          padding: var(--padding, 16px);
          transition: transform 150ms ease-out, box-shadow 150ms ease-out;
        }

        .container:hover {
          transform: translateY(-2px);
          box-shadow: var(--shadow-hover, 0 4px 8px rgba(0,0,0,0.1));
        }

        ::slotted(*) {
          margin-bottom: var(--item-spacing, 8px);
        }

        ::slotted(*:last-child) {
          margin-bottom: 0;
        }
      `</span>;
      <span class="hljs-built_in">this</span>.shadow.appendChild(enhancedStyle);
    });
  }
}

customElements.define(<span class="hljs-string">'performance-optimized-component'</span>, PerformanceOptimizedComponent);
</code></pre>
<p>This architecture demonstrates several performance optimization techniques:</p>
<ul>
<li><p>Prioritization of critical rendering path styles</p>
</li>
<li><p>Deferred loading of non-critical styles</p>
</li>
<li><p>Use of requestIdleCallback for low-priority style enhancements</p>
</li>
<li><p>Minimal initial render footprint</p>
</li>
</ul>
<p>Performance is crucial when building production-grade Web Components. This demo visualizes the impact of critical rendering path optimization by showing how components can prioritize structural content while deferring non-critical styles. Watch as the component first renders with minimal styling for immediate user interaction, then progressively enhances with advanced styling features. The timeline visualization shows how the component minimizes initial render time while providing a rich interactive experience. Toggle between optimized and unoptimized versions to see the difference in rendering performance.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/ZYYEYqg">https://codepen.io/mnichols08/pen/ZYYEYqg</a></div>
<p> </p>
<h2 id="heading-integration-with-design-systems-and-component-libraries">Integration with Design Systems and Component Libraries</h2>
<p>Advanced Web Component styling often requires integration with existing design systems. This approach demonstrates a technique for adapting to popular frameworks:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AdaptiveComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() {
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'design-system'</span>];
  }

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });

    <span class="hljs-comment">// Base structure</span>
    <span class="hljs-built_in">this</span>.shadow.innerHTML = <span class="hljs-string">`
      &lt;div class="adaptive-container"&gt;
        &lt;slot&gt;&lt;/slot&gt;
      &lt;/div&gt;
    `</span>;

    <span class="hljs-comment">// Initialize with default styling</span>
    <span class="hljs-built_in">this</span>._applyDesignSystem();
  }

  attributeChangedCallback(name, oldValue, newValue) {
    <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'design-system'</span> &amp;&amp; oldValue !== newValue) {
      <span class="hljs-built_in">this</span>._applyDesignSystem();
    }
  }

  _applyDesignSystem() {
    <span class="hljs-keyword">const</span> system = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'design-system'</span>) || <span class="hljs-string">'default'</span>;

    <span class="hljs-comment">// Remove previous design system styles</span>
    <span class="hljs-keyword">const</span> previousStyle = <span class="hljs-built_in">this</span>.shadow.querySelector(<span class="hljs-string">'.design-system-styles'</span>);
    <span class="hljs-keyword">if</span> (previousStyle) {
      previousStyle.remove();
    }

    <span class="hljs-comment">// Apply appropriate design system mapping</span>
    <span class="hljs-keyword">const</span> styleElement = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'style'</span>);
    styleElement.className = <span class="hljs-string">'design-system-styles'</span>;

    <span class="hljs-comment">// Design system adaptations</span>
    <span class="hljs-keyword">const</span> designSystemMappings = {
      <span class="hljs-string">'default'</span>: <span class="hljs-string">`
        .adaptive-container {
          font-family: system-ui, sans-serif;
          background-color: #ffffff;
          padding: 16px;
          border-radius: 4px;
          box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
      `</span>,
      <span class="hljs-string">'material'</span>: <span class="hljs-string">`
        .adaptive-container {
          font-family: Roboto, sans-serif;
          background-color: #ffffff;
          padding: 16px;
          border-radius: 4px;
          box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
        }
      `</span>,
      <span class="hljs-string">'tailwind'</span>: <span class="hljs-string">`
        .adaptive-container {
          font-family: Inter, system-ui, sans-serif;
          background-color: #ffffff;
          padding: 1rem;
          border-radius: 0.25rem;
          box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
        }
      `</span>
    };

    styleElement.textContent = designSystemMappings[system] || designSystemMappings.default;
    <span class="hljs-built_in">this</span>.shadow.prepend(styleElement);
  }
}

customElements.define(<span class="hljs-string">'adaptive-component'</span>, AdaptiveComponent);
</code></pre>
<p>This implementation showcases:</p>
<ul>
<li><p>Adaptive styling based on design system context</p>
</li>
<li><p>Dynamic style switching without component reconstruction</p>
</li>
<li><p>Preservation of component functionality across styling changes</p>
</li>
</ul>
<h2 id="heading-best-practices-for-production-grade-web-component-styling">Best Practices for Production-Grade Web Component Styling</h2>
<h3 id="heading-1-optimize-for-render-performance">1. Optimize for Render Performance</h3>
<ul>
<li><p>Use CSS containment to isolate rendering paths: <code>contain: content;</code></p>
</li>
<li><p>Apply critical CSS first, defer non-critical styles</p>
</li>
<li><p>Minimize style recalculations by batching DOM operations</p>
</li>
<li><p>Leverage constructable stylesheets for shared styles</p>
</li>
<li><p>Avoid deep DOM trees within the shadow root</p>
</li>
</ul>
<h3 id="heading-2-design-a-robust-styling-api">2. Design a Robust Styling API</h3>
<ul>
<li><p>Create a consistent naming convention for CSS custom properties</p>
</li>
<li><p>Document all customization points in component documentation</p>
</li>
<li><p>Provide sensible defaults for all custom properties</p>
</li>
<li><p>Use logical property groups (spacing, colors, typography)</p>
</li>
<li><p>Consider component variants as entry points for design system integration</p>
</li>
</ul>
<h3 id="heading-3-test-across-styling-contexts">3. Test Across Styling Contexts</h3>
<ul>
<li><p>Verify component appearance in light and dark themes</p>
</li>
<li><p>Test inherited properties like font-family and text color</p>
</li>
<li><p>Ensure responsive behavior across viewports</p>
</li>
<li><p>Validate that CSS custom properties fall back gracefully</p>
</li>
<li><p>Verify that component styling doesn't break when nested</p>
</li>
</ul>
<h3 id="heading-4-leverage-preprocessing-for-development-experience">4. Leverage Preprocessing for Development Experience</h3>
<ul>
<li><p>Consider using SASS/LESS for maintainable component styles</p>
</li>
<li><p>Build time CSS custom property optimization</p>
</li>
<li><p>Generate documentation from component style definitions</p>
</li>
<li><p>Integrate linting tools for consistent styling patterns</p>
</li>
<li><p>Implement style regression testing for component libraries</p>
</li>
</ul>
<h2 id="heading-the-intersection-of-styling-and-component-lifecycle">The Intersection of Styling and Component Lifecycle</h2>
<p>Throughout this article, we've explored sophisticated styling techniques for Web Components, but it's important to recognize that styling doesn't exist in isolation. The component's lifecycle has profound implications for when and how styles are applied, updated, and removed.</p>
<p>Key lifecycle-related styling considerations include:</p>
<ol>
<li><p><strong>Style Initialization Timing</strong>: Styles defined in the constructor are applied before the component is connected to the DOM, which can impact initial rendering performance.</p>
</li>
<li><p><strong>Dynamic Style Updates</strong>: Many components need to update their styles in response to attribute changes, props, or state changes—operations that typically occur during lifecycle events.</p>
</li>
<li><p><strong>Style Cleanup</strong>: When components are removed from the DOM, any associated stylesheets or external resources should be properly cleaned up to prevent memory leaks.</p>
</li>
</ol>
<p>Here's how component lifecycle methods relate to styling:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LifecycleAwareStyledComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() {
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'theme'</span>, <span class="hljs-string">'size'</span>, <span class="hljs-string">'disabled'</span>];
  }

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>();
    <span class="hljs-built_in">this</span>.shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });

    <span class="hljs-comment">// Initial styles applied during construction</span>
    <span class="hljs-built_in">this</span>._createBaseStyles();
  }

  connectedCallback() {
    <span class="hljs-comment">// Apply context-dependent styles when added to DOM</span>
    <span class="hljs-built_in">this</span>._applyContextualStyles();

    <span class="hljs-comment">// Initialize theme from current document context</span>
    <span class="hljs-built_in">this</span>._synchronizeWithGlobalTheme();

    <span class="hljs-comment">// Add event listeners for theme changes</span>
    <span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'theme-changed'</span>, <span class="hljs-built_in">this</span>._handleThemeChange);
  }

  attributeChangedCallback(name, oldValue, newValue) {
    <span class="hljs-keyword">if</span> (oldValue === newValue) <span class="hljs-keyword">return</span>;

    <span class="hljs-comment">// Update styles based on attribute changes</span>
    <span class="hljs-keyword">switch</span>(name) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">'theme'</span>:
        <span class="hljs-built_in">this</span>._updateThemeStyles(newValue);
        <span class="hljs-keyword">break</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">'size'</span>:
        <span class="hljs-built_in">this</span>._updateSizeStyles(newValue);
        <span class="hljs-keyword">break</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">'disabled'</span>:
        <span class="hljs-built_in">this</span>._updateDisabledState(newValue !== <span class="hljs-literal">null</span>);
        <span class="hljs-keyword">break</span>;
    }
  }

  disconnectedCallback() {
    <span class="hljs-comment">// Clean up any theme listeners</span>
    <span class="hljs-built_in">document</span>.removeEventListener(<span class="hljs-string">'theme-changed'</span>, <span class="hljs-built_in">this</span>._handleThemeChange);

    <span class="hljs-comment">// Remove any dynamically added stylesheets</span>
    <span class="hljs-comment">// (important for preventing memory leaks)</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>._adoptedStyleSheet) {
      <span class="hljs-built_in">this</span>.shadow.adoptedStyleSheets = 
        <span class="hljs-built_in">this</span>.shadow.adoptedStyleSheets.filter(<span class="hljs-function"><span class="hljs-params">sheet</span> =&gt;</span> sheet !== <span class="hljs-built_in">this</span>._adoptedStyleSheet);
    }
  }

  <span class="hljs-comment">// Component-specific implementation of style methods...</span>
}
</code></pre>
<p>This intricate relationship between styling and lifecycle is precisely why a comprehensive understanding of both is essential for building robust Web Components. In our next article, we'll dive deep into lifecycle methods and component communication patterns, exploring how they work in concert with the styling techniques covered here.</p>
<h2 id="heading-conclusion-building-a-cohesive-component-ecosystem">Conclusion: Building a Cohesive Component Ecosystem</h2>
<p>Effective styling of Web Components requires a strategic approach that balances technical requirements with developer and user experience. By implementing Shadow DOM encapsulation, leveraging CSS custom properties for customization, and applying performance optimization techniques, you can create a cohesive component ecosystem that scales across applications.</p>
<p>The techniques described in this article provide a foundation for building sophisticated component libraries that maintain visual consistency while offering flexibility for diverse implementation contexts. However, styling is just one pillar of the Web Components architecture.</p>
<p>To build truly robust components, this styling foundation must work in concert with proper lifecycle management and communication patterns. Components need to know not just how to style themselves, but when to apply those styles and how to communicate style changes to other components in the system.</p>
<p>With a robust styling approach integrated with proper lifecycle management, your Web Components become not just isolated widgets but integral parts of a cohesive design language—enabling true component-driven development at scale.</p>
<p><a target="_blank" href="https://github.com/mnichols08/WebComponentStyles">Demonstration GitHub Repository</a><br /><a target="_blank" href="https://mnichols08.github.io/WebComponentStyles/">Demonstration Deployment</a></p>
]]></content:encoded></item><item><title><![CDATA[How Templates and Slots Work]]></title><description><![CDATA[Explore HTML's `<template>` and `<slot>` elements to create modular, maintainable web components with reusable content and dynamic content insertion]]></description><link>https://journeytocode.hashnode.dev/how-templates-and-slots-work</link><guid isPermaLink="true">https://journeytocode.hashnode.dev/how-templates-and-slots-work</guid><category><![CDATA[ShadowDOM]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[templates]]></category><category><![CDATA[slots]]></category><dc:creator><![CDATA[Mikey Nichols]]></dc:creator><pubDate>Sun, 09 Mar 2025 05:00:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741535151551/1bd42074-8d50-4687-8df7-b8c2c44c97a6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>When developing for the web today, it is crucial to write modular and maintainable code. Web components offer a powerful set of tools consisting of <code>&lt;template&gt;</code>s and <code>&lt;slot&gt;</code>s, which allow us to define reusable content structures and facilitate dynamic content insertion within a web component. In this article, we explore the functionalities of these elements and demonstrate how they can be leveraged to build highly reusable user interface elements that are native to HTML.</p>
<hr />
<h2 id="heading-the-element">The <code>&lt;template&gt;</code> Element</h2>
<p>The <code>&lt;template&gt;</code> element allows us to define HTML fragments that are not rendered immediately when the page loads. Instead, these fragments can be instantiated and inserted into the DOM at runtime using JavaScript. This approach promotes code reuse and helps maintain a clean and efficient DOM structure.</p>
<p><strong>Example:</strong></p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"my-template"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-class">.message</span> {
      <span class="hljs-attribute">color</span>: blue;
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">20px</span>;
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"message"</span>&gt;</span>Hello, Friend!<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<p>In this example, the <code>&lt;template&gt;</code> element contains a styled <code>&lt;div&gt;</code> that can be reused multiple times within the document.</p>
<p><strong>Utilizing the Template in JavaScript:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Access the template element</span>
<span class="hljs-keyword">const</span> template = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'my-template'</span>);

<span class="hljs-comment">// Clone the content of the template</span>
<span class="hljs-keyword">const</span> clone = <span class="hljs-built_in">document</span>.importNode(template.content, <span class="hljs-literal">true</span>);

<span class="hljs-comment">// Append the cloned content to the body</span>
<span class="hljs-built_in">document</span>.body.appendChild(clone);
</code></pre>
<p>Here, the template's content is cloned and appended to the document's body, rendering the defined message.</p>
<p>If we want to load a web component <strong>without embedding the HTML template directly in the file</strong>, we can dynamically fetch the HTML template from an external source (such as a separate HTML file, a server endpoint, or a CDN). This approach is useful for:</p>
<ol>
<li><p><strong>Modularity</strong>: Keep our HTML templates separate from our JavaScript logic.</p>
</li>
<li><p><strong>Lazy Loading</strong>: Load templates only when needed, improving initial page load performance.</p>
</li>
<li><p><strong>Reusability</strong>: Share templates across multiple components or projects.</p>
</li>
</ol>
<p>Here is an interactive demo of cloning a template element like we learned above</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/OPJBKZv">https://codepen.io/mnichols08/pen/OPJBKZv</a></div>
<p> </p>
<h3 id="heading-fetch-the-template-from-an-external-html-file"><strong>Fetch the Template from an External HTML File</strong></h3>
<p>We can store our HTML template in a separate file (e.g., <code>template.html</code>) and fetch it using JavaScript.</p>
<h4 id="heading-example">Example:</h4>
<ul>
<li><p><code>template.html</code> (external file):</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"my-template"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"title"</span>&gt;</span>Default Title<span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"content"</span>&gt;</span>Default content goes here.<span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
</li>
<li><p><strong>JavaScript for Web Component</strong>:</p>
<pre><code class="lang-javascript">  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>();
      <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    }

    <span class="hljs-keyword">async</span> connectedCallback() {
      <span class="hljs-comment">// Fetch the external template (replace 'path/to/' with the actual path)</span>
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'path/to/template.html'</span>);
      <span class="hljs-keyword">const</span> text = <span class="hljs-keyword">await</span> response.text();

      <span class="hljs-comment">// Parse the fetched HTML and extract the template</span>
      <span class="hljs-keyword">const</span> parser = <span class="hljs-keyword">new</span> DOMParser();
      <span class="hljs-keyword">const</span> doc = parser.parseFromString(text, <span class="hljs-string">'text/html'</span>);
      <span class="hljs-keyword">const</span> template = doc.getElementById(<span class="hljs-string">'my-template'</span>).content;

      <span class="hljs-comment">// Clone and append the template to the shadow DOM</span>
      <span class="hljs-built_in">this</span>.shadowRoot.appendChild(template.cloneNode(<span class="hljs-literal">true</span>));
    }
  }

  customElements.define(<span class="hljs-string">'my-component'</span>, MyComponent);
</code></pre>
</li>
</ul>
<p>This example shows how to swap different templates at runtime to completely change a component's appearance while preserving its content.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/LEYgwrR">https://codepen.io/mnichols08/pen/LEYgwrR</a></div>
<p> </p>
<h3 id="heading-avoid-deprecated"><strong>Avoid Deprecated</strong> <code>&lt;link rel="import"&gt;</code></h3>
<p>HTML imports (<code>&lt;link rel="import"&gt;</code>) were once used to load external HTML files, but this feature is <strong>deprecated</strong> and unsupported in modern browsers. While we may encounter it in legacy code, avoid using this approach.</p>
<h3 id="heading-use-javascript-modules-and-template-literals"><strong>Use JavaScript Modules and Template Literals</strong></h3>
<p>If we prefer not to fetch templates from external files, we can also define them as strings in JavaScript modules.</p>
<h4 id="heading-example-1">Example:</h4>
<ul>
<li><p><strong>JavaScript Module</strong>:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> templateString = <span class="hljs-string">`
    &lt;template id="my-template"&gt;
      &lt;div class="card"&gt;
        &lt;h2&gt;&lt;slot name="title"&gt;Default Title&lt;/slot&gt;&lt;/h2&gt;
        &lt;p&gt;&lt;slot name="content"&gt;Default content goes here.&lt;/slot&gt;&lt;/p&gt;
      &lt;/div&gt;
    &lt;/template&gt;
  `</span>;

  <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTemplate</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> parser = <span class="hljs-keyword">new</span> DOMParser();
    <span class="hljs-keyword">const</span> doc = parser.parseFromString(templateString, <span class="hljs-string">'text/html'</span>);
    <span class="hljs-keyword">return</span> doc.getElementById(<span class="hljs-string">'my-template'</span>).content;
  }
</code></pre>
</li>
<li><p><strong>Web Component</strong>:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">import</span> { getTemplate } <span class="hljs-keyword">from</span> <span class="hljs-string">'./template.js'</span>;

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>();
      <span class="hljs-keyword">const</span> shadowRoot = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
      <span class="hljs-keyword">const</span> template = getTemplate();
      shadowRoot.appendChild(template.cloneNode(<span class="hljs-literal">true</span>));
    }
  }

  customElements.define(<span class="hljs-string">'my-component'</span>, MyComponent);
</code></pre>
</li>
</ul>
<hr />
<h2 id="heading-the-element-1">The <code>&lt;slot&gt;</code> Element</h2>
<p>The <code>&lt;slot&gt;</code> element serves as a placeholder within a web component's shadow DOM, allowing us to define where external content (light DOM) should be inserted. This mechanism enables the creation of flexible and customizable components that can accept and display varying content provided by the component's user.</p>
<p><strong>Example:</strong></p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Define the custom element --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"my-component-template"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-class">.container</span> {
      <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>;
      <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>;
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"header"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is the main content of the component.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"footer"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>();
      <span class="hljs-keyword">const</span> template = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'my-component-template'</span>).content;
      <span class="hljs-keyword">const</span> shadowRoot = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
      shadowRoot.appendChild(template.cloneNode(<span class="hljs-literal">true</span>));
    }
  }

  customElements.define(<span class="hljs-string">'my-component'</span>, MyComponent);
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Use the custom element with slotted content --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">my-component</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"header"</span>&gt;</span>Welcome!<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"footer"</span>&gt;</span>Thanks for visiting.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">my-component</span>&gt;</span>
</code></pre>
<p>In this example, the <code>&lt;my-component&gt;</code> element defines slots named "header" and "footer." When the component is used, content is assigned to these slots using the <code>slot</code> attribute, enabling dynamic insertion of external content into the component's structure.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/mydzNjB">https://codepen.io/mnichols08/pen/mydzNjB</a></div>
<p> </p>
<hr />
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/MYWPNBM">https://codepen.io/mnichols08/pen/MYWPNBM</a></div>
<p> </p>
<h2 id="heading-combining-and-for-dynamic-components">Combining <code>&lt;template&gt;</code> and <code>&lt;slot&gt;</code> for Dynamic Components</h2>
<p>By integrating <code>&lt;template&gt;</code> and <code>&lt;slot&gt;</code>, we can create dynamic and reusable components that maintain a clear separation between structure and content.</p>
<p><strong>Example:</strong></p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"card-template"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-class">.card</span> {
      <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ddd</span>;
      <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>;
      <span class="hljs-attribute">padding</span>: <span class="hljs-number">15px</span>;
      <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">2px</span> <span class="hljs-number">2px</span> <span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"title"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"content"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CardComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>();
      <span class="hljs-keyword">const</span> template = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'card-template'</span>).content;
      <span class="hljs-keyword">const</span> shadowRoot = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
      shadowRoot.appendChild(template.cloneNode(<span class="hljs-literal">true</span>));
    }
  }

  customElements.define(<span class="hljs-string">'card-component'</span>, CardComponent);
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Using the card component --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">card-component</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"title"</span>&gt;</span>Card Title<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"content"</span>&gt;</span>This is the content of the card.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">card-component</span>&gt;</span>
</code></pre>
<p>Here, the <code>CardComponent</code> uses a template to define its structure and styling. The slots "title" and "content" allow users to insert custom content, making the component reusable and adaptable.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/VYwEoGb">https://codepen.io/mnichols08/pen/VYwEoGb</a></div>
<p> </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/wBvYVEx">https://codepen.io/mnichols08/pen/wBvYVEx</a></div>
<p> </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/mnichols08/pen/EaxdqdV">https://codepen.io/mnichols08/pen/EaxdqdV</a></div>
<p> </p>
<p>These examples showcase the versatility of templates and slots beyond basic usage. <strong>Slots Fallback Content</strong> creates resilient components with sensible defaults, improving developer experience while maintaining visual consistency. <strong>Slot Events</strong> transform components from passive containers into reactive elements that adapt to their content, though careful attention to timing is essential when working with the <code>slotchange</code> event. <strong>Templates Inside Components</strong> demonstrate composition at its best, enabling dynamic interfaces that render different structures based on data or user interaction—particularly valuable for complex interfaces that must adapt to changing requirements.</p>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>The <code>&lt;template&gt;</code> and <code>&lt;slot&gt;</code> elements are fundamental tools for building modular and dynamic web components. The <code>&lt;template&gt;</code> element enables the definition of reusable HTML fragments, while the <code>&lt;slot&gt;</code> element facilitates the insertion of external content into predefined placeholders. By leveraging these elements, developers can create flexible, maintainable, and encapsulated components that enhance the structure and functionality of web applications.</p>
]]></content:encoded></item></channel></rss>