<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by EisenbergEffect on Medium]]></title>
        <description><![CDATA[Stories by EisenbergEffect on Medium]]></description>
        <link>https://medium.com/@eisenbergeffect?source=rss-257e6cfa66b3------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Bp1EaHBSSYW4gwO_DbKmDA.jpeg</url>
            <title>Stories by EisenbergEffect on Medium</title>
            <link>https://medium.com/@eisenbergeffect?source=rss-257e6cfa66b3------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 07 May 2026 08:51:06 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@eisenbergeffect/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Good Tidings!]]></title>
            <link>https://eisenbergeffect.medium.com/good-tidings-6f435c8162f3?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/6f435c8162f3</guid>
            <category><![CDATA[web-standards]]></category>
            <category><![CDATA[ui-engineering]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[sales]]></category>
            <category><![CDATA[web-components]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Mon, 08 Dec 2025 14:16:30 GMT</pubDate>
            <atom:updated>2025-12-08T14:16:30.281Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1J1kjGVkpvx-ADVo4ug07g.png" /></figure><p>I heard from quite a few people who were excited about the Web Component Engineering course but missed the Thanksgiving sale window. So, as we wrap up the year and head into a new one, I’m running <strong>one more sale</strong> as a bit of year-end / new-year “good tidings.”</p><p>From now through the first week of the new year, you can get <strong>25% off</strong> <a href="https://bluespire.com/course/web-component-engineering">my Web Component Engineering course</a> when you use the discount code <strong>GOODTIDINGS25</strong> at checkout.</p><h3>About the Course</h3><p><a href="https://bluespire.com/course/web-component-engineering/">Web Component Engineering</a> is a self-paced, in-depth course on modern UI engineering through the lens of Web Components and core Web Standards. It’s designed to help you move beyond framework-only knowledge and really understand the Web Platform you’re building on.</p><p>You’ll get a deep dive into topics like:</p><ul><li>Using and authoring Web Components in real applications</li><li>Working directly with DOM APIs instead of only through frameworks and abstractions</li><li>Modern CSS for robust, scalable UI systems (including Shadow DOM styling, container queries, and more)</li><li>Accessibility as a first-class concern in component design</li><li>Form-associated custom elements and integrating components with real forms</li><li>Design systems, tokens, and component libraries built on Web Components</li><li>Application architecture, routing, and app shells with Web Components</li><li>Tools and libraries like Storybook, Playwright, Lit, FAST, and others you’ll use in production</li></ul><p>The course is trusted by engineers at top companies and is designed to be the “missing manual” for serious UI engineers who want to master the modern Web Platform.</p><h3>What You Get</h3><p>When you enroll, you get access to:</p><ul><li>13 modules with over 170 lessons, fully self-paced</li><li>A custom interactive learning app that lets you follow along, run demos, and experiment in the browser with no setup</li><li>Runnable examples, downloadable notes, and reference materials</li><li>A Certificate of Completion you can share with your team or manager</li></ul><p>Whether you’re building a design system, maintaining a complex front-end, or just want to understand what’s really possible with modern Web Standards, this is designed to meet you where you are and push you to the next level.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4iAAqIV2CXYGu6gpWP0TxA.png" /><figcaption>The Interactive Learning Experience</figcaption></figure><h3>Sale Details</h3><ul><li><strong>Discount:</strong> 25% off</li><li><strong>Code:</strong> <strong>GOODTIDINGS25</strong></li><li><strong>Valid through:</strong> The end of the first week of the new year</li><li><strong>Course:</strong> <a href="https://bluespire.com/course/web-component-engineering/?utm_source=chatgpt.com">https://bluespire.com/course/web-component-engineering/</a></li></ul><p>If you missed the Thanksgiving window, or you’ve been on the fence, this is another chance to invest in your skills and deepen your understanding of the Web Platform.</p><p>Thanks for being part of the Web community, and here’s to a great year of building better UIs with Web Standards.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6f435c8162f3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Thanksgiving Sale]]></title>
            <link>https://eisenbergeffect.medium.com/thanksgiving-sale-a5b6b64562e2?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/a5b6b64562e2</guid>
            <category><![CDATA[online-courses]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[web-standards]]></category>
            <category><![CDATA[sales]]></category>
            <category><![CDATA[web-components]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Mon, 24 Nov 2025 14:02:51 GMT</pubDate>
            <atom:updated>2025-11-24T14:02:51.848Z</atom:updated>
            <content:encoded><![CDATA[<h3>Thanksgiving Sale: 25% Off Web Component Engineering</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tWStmOQo-oB7xdp3PotIIQ.png" /></figure><p>I’m wrapping up the year with a little thank-you to everyone who cares about the Web Platform and modern UI engineering.</p><p>From now through <strong>November 30</strong>, you can get <strong>25% off</strong> my Web Component Engineering course when you use the discount code <strong>GRATITUDE25</strong> at checkout.</p><p>👉 <a href="https://bluespire.com/course/web-component-engineering/">Visit the course page</a>.</p><h3>About the Course</h3><p>The Web Component Engineering course is a self-paced, in-depth course that teaches modern UI engineering through the lens of Web Components and core Web Standards. It’s designed to help you move beyond “framework-only” knowledge and really understand the platform you’re building on.</p><h4>What It Covers</h4><ul><li>Using and authoring Web Components</li><li>DOM APIs and working directly with the DOM</li><li>Modern CSS for real-world UI systems</li><li>Accessibility baked into component design</li><li>Form-associated custom elements and handling real forms</li><li>Design systems, tokens, and component libraries</li><li>Application architecture with Web Components</li><li>Modern tools and libraries (Storybook, Playwright, Lit, FAST, and more)</li></ul><h4>What You Get</h4><ul><li>13 modules with over 170 videos, fully self-paced</li><li>An interactive in-browser learning app (no local setup required)</li><li>Runnable demos, slides, and downloadable notes</li><li>A certificate of completion you can share with your team or manager</li></ul><p>Whether you’re building a design system, maintaining a complex front-end application, or just want to understand what’s really possible with modern Web Standards, this course is designed to meet you where you are and then push you to the next level.</p><h3>Thanksgiving Sale Details</h3><ul><li><strong>Discount:</strong> 25% off</li><li><strong>Code:</strong> <strong>GRATITUDE25</strong></li><li><strong>Valid through:</strong> November 30</li><li><strong>Course page:</strong> <a href="https://bluespire.com/course/web-component-engineering/?utm_source=chatgpt.com">https://bluespire.com/course/web-component-engineering/</a></li></ul><p>If you’ve been waiting for the right moment to dive into Web Components and modern UI engineering, now’s the time to enroll.</p><p>Happy Thanksgiving, and thanks for being part of the Web community. 🙏</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a5b6b64562e2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Default Isn’t Design]]></title>
            <link>https://eisenbergeffect.medium.com/default-isnt-design-24df33272abb?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/24df33272abb</guid>
            <category><![CDATA[web-standards]]></category>
            <category><![CDATA[javascript-frameworks]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[engineering-leadership]]></category>
            <category><![CDATA[engineering-culture]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Mon, 13 Oct 2025 14:03:23 GMT</pubDate>
            <atom:updated>2025-10-18T17:16:28.951Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oOlje1_ScTPvCamUOBmiiQ.jpeg" /></figure><p>Framework monoculture is a psychology problem as much as a tech problem. When one approach becomes “how things are done,” we unconsciously defend it even when standards would give us a healthier, more interoperable ecosystem. Psychologists call this reflex <strong>System Justification</strong>. Naming it helps us steer toward a standards-first future without turning the discussion into a framework war.</p><p>I’ve been sitting with this idea for a while, especially in React-centric conversations where familiarity is often treated as proof. Over the past year, a run of blog posts, conference talks, YouTube videos, and a few interactions with standards-oriented groups nudged me to finally write this down. This isn’t a takedown of any project or community; it’s a look at a human reflex many of us share when our tools become our identity.</p><p>My aim here is simple: explain the psychology (briefly), show how it maps to front-end habits, name the quiet costs of monoculture, and offer a pragmatic path toward a standards-first posture that still meets teams where they are.</p><h3>System Justification</h3><p>In the 1990s, social psychologists (notably John Jost and Mahzarin Banaji) described <strong>System Justification Theory (SJT)</strong> as the idea that people are motivated to see existing arrangements as legitimate, fair, and desirable even when those arrangements have obvious downsides or are downright harmful. In other words, it’s a matter of familiarity. Individuals and groups defend the status quo because it reduces uncertainty, protects identity, and keeps members aligned with their groups.</p><p>SJT highlights three motives that drive this defense:</p><ul><li><strong>Epistemic</strong>: <em>The comfort of being right.</em><br>Clear rules and familiar patterns feel safer than ambiguity.</li><li><strong>Existential</strong>: T<em>he comfort of safety.</em><strong><br></strong>It’s less frightening to believe the world you’ve invested in is basically true, good, and beautiful.</li><li><strong>Relational</strong>: <em>The comfort of acceptance.</em><br>Agreeing with your community keeps you inside the circle.</li></ul><p>If you scan history, you’ll spot the same pattern over and over again:</p><ul><li>Early resistance to handwashing in medicine (the “Semmelweis reflex”).</li><li>Decades of public minimization around tobacco harms.</li><li>Long, loud debates over seatbelts and other safety standards.</li><li>“Separate but equal” as a rationale for inequality masked as order.</li><li>And many more (see appendix)…</li></ul><p>None of these debates were purely about evidence; they were about identity, habit, and social standing.</p><p>Now translate that to the web. When a single toolset becomes <em>the</em> default, we don’t just prefer it, we build narratives that <strong>justify</strong> it. And that’s when a tool quietly becomes a gate or even a destructive force.</p><h3>Subtle Reflex</h3><p>System justification doesn’t announce itself. It shows up as common-sense statements, subtle status signals, and “everyone knows” assumptions:</p><ul><li><strong>“This is how real apps are built.”<br></strong>The present method masquerades as natural law. Alternatives become “experimental,” “academic,” “toy,” or “fringe” even when they align with widely adopted standards.</li><li><strong>“Standards are idealistic; we ship.”<br></strong>Shipping is good. But this framing sneaks in a false tradeoff: standards are painted as purity projects rather than the bedrock that lets different teams, tools, and runtimes collaborate without friction.</li><li><strong>“Our audience is modern, so the costs don’t matter.”<br></strong>Hidden costs (bundle weight, device inequity, hydration overhead, portability constraints) get discounted as someone else’s problem. Often that tends to be users with older devices, spotty networks, or that require assistive technology.</li><li><strong>“Everyone on the team knows this stack.”<br></strong>Familiarity becomes proof of superiority. The fact that a community is large and helpful (a real benefit!) mutates into the claim that alternatives are unworthy.</li><li><strong>“If it were better, we’d already be doing it.”<br></strong>This is injunctification: confusing “what is” with “what should be.” At this point, the comfort of the status quo has become its own argument.</li></ul><p>Behind these lines are those three motives I mentioned before: the desire to be right, safe, and accepted. Notice how much of the rhetoric protects identity (“we’re experts in X”) and group cohesion (“this is who we are”) rather than debating technical merit or user impact.</p><h3>Quiet Costs</h3><p>When one approach becomes the unexamined default, you don’t just get consistency, you also accumulate opportunity costs that are easy to miss in the moment:</p><ul><li><strong>Slower standards adoption.</strong><br>If your core primitives exist only inside a single framework abstraction, other surfaces can’t reuse them without translation. That discourages cross-project collaboration and fragments the commonality that standards provide.</li><li><strong>Less innovation.</strong><br>Monoculture makes it harder to try architecture patterns that assume standards first (progressive enhancement, islands, simpler render paths). The conversation shifts from “what does the platform already give us?” to “how do we shoehorn the platform into our preselected model?”</li><li><strong>Reduced portability and value.</strong><br>Work tightly bound to one frontend framework is harder to move between products, partners, and future platforms. Today’s convenience turns into tomorrow’s migration tax.</li><li><strong>Fragility in supply chains.</strong><br>When large chunks of your UI or data flow depend on a specific ecosystem, you inherit its release cadence, deprecations, and politics. If that ecosystem’s priorities diverge from yours, you feel it.</li><li><strong>Talent pipeline brittleness.</strong><br>Hiring “only X framework” narrows who can contribute. Onboarding becomes about memorizing a house style instead of leveraging the web and public contracts. That’s great until you need people who didn’t grow up inside the same assumptions.</li><li><strong>User-shifted costs.</strong><br>Developer convenience is meaningful, but when it consistently beats standards-based reuse and progressive delivery, users pay in bandwidth, energy, accessibility, and responsiveness.</li></ul><p>These rarely show up in a single PR, but <strong>they accumulate as costly drift.</strong></p><h3>Standards First</h3><p>This isn’t a call to abandon frameworks. It’s a call to <strong>re-center standards as the foundation </strong>and treat frameworks as <strong>adapters</strong> that improve ergonomics where helpful. Think of it as a “shift left” of front-end.</p><ul><li><strong>Publish primitives as standard-compliant units.</strong><br>Think clear public contracts: documented interfaces, events, and styling hooks that any framework can build on.</li><li><strong>Offer adapters for popular stacks.</strong><br>Keep the ergonomic sugar where teams want it without making the framework adapter the source of truth.</li><li><strong>Document the contract, not the adapter.</strong><br>Make the standard interface the canonical reference. Adapters will come and go but the contract should endure.</li><li><strong>Measure portability.</strong><br>Track reuse across projects/frameworks, the thinness of adapters (no duplicated logic), and the performance and accessibility characteristics of the <em>standard</em> layer.</li></ul><p>The goal isn’t to win a framework debate. It’s to make your work lean, portable, and future friendly.</p><h3>Objections…Answered</h3><p>Here are a few common objections I’ve heard over the years, each accompanied by a brief reply.</p><ul><li><strong>“Developer experience will get worse.”<br></strong>Good DX belongs at the edges. Keep rich types, ergonomics, and editor integrations in your adapters, but anchor correctness, semantics, and accessibility in the standards-level contract. You’re moving convenience, not sacrificing it.</li><li><strong>“Standards move too slowly.”<br></strong>Stability is a feature at the primitive layer. You can still innovate rapidly in composition, architecture, and tooling. Just don’t fork the base. When standards evolve, your contract benefits everywhere at once instead of patch-by-patch in each framework.</li><li><strong>“This will slow shipping.”<br></strong>The first few primitives take discipline. After that, reuse pays compound interest especially across multiple apps or surfaces. If you measure <em>total</em> delivery (including maintenance and cross-team portability), standards-first usually wins on time.</li><li><strong>“No one is asking for this.”<br></strong>Portability is like backups: ignored until it isn’t. Partners, acquisitions, re-platforming, and new surfaces make “runs anywhere” suddenly priceless. You don’t get that value by accident, only by design.</li><li><strong>“Our team doesn’t know how to do it.”<br></strong>Start small. Write one simple, well-documented contract and a pair of adapters. Share the recipe. Most of the skill is in writing clear interfaces and testing behavior, skills your team already has.</li><li><strong>“We’ll split the community.”<br></strong>Standards-first is a unifier. It invites <em>more</em> communities to participate because the core is neutral. The conversation shifts from “which framework is allowed?” to “how do we make the contract great?”</li><li><strong>“The performance gains aren’t guaranteed.”<br></strong> True…and that’s why you measure user-visible outcomes (latency to first interaction, responsiveness, energy use) at the standard layer. If an adapter adds weight, it’s localized and replaceable; the base remains lean.</li><li><strong>“Security and compliance will be harder.”<br></strong>The opposite. A single, well-documented contract simplifies auditing, testing, and threat modeling. You assess the primitive once and reuse the assurance everywhere, instead of auditing n copies of the same idea.</li></ul><h3>Default Defense</h3><p>Here are some practical ways to guard against a default framework monoculture, whether on your keyboard, in a design review, or during a meeting.</p><h4>Spot the Tell</h4><p>Listen for “comfort” language. It’s a red flag that should prompt us to pause and examine. Here are a few common patterns:</p><ul><li><strong>Injunctification<br></strong>“That’s just how it’s done.” or “Real apps do X.”</li><li><strong>Appeal to Familiarity<br></strong>“Everyone on our team knows this already.”</li><li><strong>Status Through Popularity<br></strong>“The ecosystem is huge, so it must be the best.”</li><li><strong>Minimizing User Costs<br></strong>“Our users are on modern devices anyway.”</li><li><strong>False Dichotomy<br></strong>“Standards are idealistic; we ship.”</li><li><strong>Sunk Cost<br></strong>“We’ve already invested too much to change now.”</li><li><strong>Exaggeration<br></strong>“If we don’t do it this way, everything else breaks.”</li><li><strong>Security Blanket<br></strong>“Compliance will hate anything new.”</li><li><strong>Preemptive Defeat<br></strong>“If this were better, we’d already be doing it.”</li></ul><p>When you hear one of these, ask: <em>Is this defending identity or describing reality?</em></p><h4>Ask Yourself</h4><p>Use the following as a mental checklist to help yourself steer clear of the default trap:</p><ul><li><strong>Counterfactual<br></strong><em>Would I still believe this if my preferred framework/tool didn’t exist?</em></li><li><strong>User-first<br></strong><em>Where do users pay for this choice (latency, accessibility, bandwidth, portability)?</em></li><li><strong>Contract<br></strong><em>Can I articulate the standard-level contract independent of any framework?</em></li><li><strong>Opportunity<br></strong><em>What future paths does this decision close? What does it keep open?</em></li><li><strong>Evidence<br></strong><em>Do we have a measured outcome, or just a story that feels safe?</em></li><li><strong>Reversibility<br></strong><em>If we’re wrong, how hard is this to undo?</em></li><li><strong>Portability<br></strong><em>Could a team using another framework consume this work without needing to rewrite it?</em></li></ul><h4>Reframe It</h4><p>Try to turn status-quo defenses into standards-first questions without picking a fight.</p><ul><li><strong>From:</strong> “This is how we build apps.”<br><strong>To:</strong> “What’s the smallest standard contract here, and how would frameworks map to it?”</li><li><strong>From:</strong> “DX will tank if we move away from X.”<br><strong>To:</strong> “How do we keep great DX at the edges while grounding behavior in a standard interface?”</li><li><strong>From:</strong> “No one’s asking for portability.”<br><strong>To:</strong> “Which upcoming surfaces/partners would benefit if this were portable by default?”</li><li><strong>From:</strong> “Security won’t allow it.”<br><strong>To:</strong> “Can we write the contract so security can audit it once and bless all adapters?”</li><li><strong>From:</strong> “It’s too late to change.”<br><strong>To:</strong> “What thin seam lets us pilot a standard-level primitive alongside what we have?”</li><li><strong>From:</strong> “This pattern is proven; everyone uses it.”<br><strong>To:</strong> “Popularity does not equal portability. Can we prove the contract stands on its own?”</li><li><strong>From:</strong> “Performance is fine on modern phones.”<br><strong>To:</strong> “Let’s test on slow hardware and unreliable networks. If the framework adapter adds cost, can the standard base stay lean?”</li></ul><h3>Final Thoughts</h3><p>This isn’t about shaming individual or team preferences. It’s about noticing when preference hardens into dogma, and gently pivoting the conversation back to <strong>standards, contracts, and <em>users</em></strong>.</p><p>Frameworks come and go; the web remains. If we can notice the human urge to defend “how we do things” simply because it’s how we do things, we can aim that energy at building a stronger foundation: <strong>standards first, adapters as needed, and <em>design always</em></strong>.</p><p>If you maintain a design system, library, or toolkit, consider publishing your primitives to a clear, standards-oriented contract and inviting framework and tool adapter authors to the table. That’s not anti-framework; it’s a pro-web approach that widens the path for everyone.</p><h3>Appendix: Historical SJT Examples</h3><p>A few examples of SJT in recent history.</p><h4>Medicine and Science</h4><ul><li><strong>Germ theory and asepsis (post-Semmelweis)<br></strong>Early pushback defended established routines and senior authority. <br><em>Tell:</em> “We’ve always done it this way; outcomes are fine.”</li><li><strong>Stomach ulcers (H. pylori)<br></strong>The “stress/spice” story persisted long after bacterial evidence emerged. <em>Tell:</em> Familiar theory felt truer than revision.</li><li><strong>Bloodletting as standard care<br></strong>Centuries of inertial legitimacy despite mounting counterevidence.<br><em>Tell:</em> Tradition = truth.</li><li><strong>Lobotomy in psychiatry<br></strong>Entrenched as “last resort” therapy before antipsychotics, long past ethical/clinical doubts. <br><em>Tell:</em> Harm reframed as necessary tradeoff.</li></ul><h4>Public Health and Safety</h4><ul><li><strong>Smoking in public spaces</strong> <br>“Personal freedom” and “ventilation is enough” narratives resisted smoke-free rules. <br><em>Tell:</em> Costs shifted to bystanders, normalized as choice.</li><li><strong>Leaded gasoline<br></strong>Industry reassurances delayed phase-out despite environmental/health data. <br><em>Tell:</em> System seen as safe because it’s ubiquitous.</li><li><strong>Asbestos</strong> <br>“Proper handling makes it fine” slowed bans. <br><em>Tell:</em> Diffusion of responsibility (“we follow the guidelines”).</li><li><strong>Seatbelts, airbags, helmets<br></strong>“Restrictive,” “unsafe in rare cases,” “I’m a careful driver.” <br><em>Tell:</em> Anecdotes outrank data.</li></ul><h4>Environment and Energy</h4><ul><li><strong>CFCs and the ozone hole<br></strong>Early minimization framed changes as economically catastrophic. <br><em>Tell:</em> “Not proven” + “too costly” = defend status quo.</li><li><strong>Climate change denialism<br></strong>Identity, jobs, and regional economies tie to existing energy systems. <em>Tell:</em> Evidence filtered through group belonging.</li></ul><h4>Civil Rights and Social Policy</h4><ul><li><strong>“Separate but equal”</strong><br>Inequality rationalized as order and tradition. <br><em>Tell:</em> Stability over disruption.</li><li><strong>Child labor<br></strong>Framed as “builds character” and economic necessity before reforms. <em>Tell:</em> Harms recast as virtues.</li></ul><h4>Technology and Work</h4><ul><li><strong>“Office-only” culture</strong> <br>Remote work dismissed as unserious or unproductive. <br><em>Tell:</em> Familiar practice = proof; counterexamples waved away.</li><li><strong>Proprietary file formats<br></strong>“Ecosystem integration” used to justify lock-in over open standards. <br><em>Tell:</em> Convenience for insiders outweighs portability for everyone.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=24df33272abb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Announcing Coaching, Mentoring, and Consulting Opportunities]]></title>
            <link>https://eisenbergeffect.medium.com/announcing-coaching-mentoring-and-consulting-opportunities-f272b7f58131?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/f272b7f58131</guid>
            <category><![CDATA[consulting]]></category>
            <category><![CDATA[mentoring]]></category>
            <category><![CDATA[coaching]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[web]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Tue, 04 Jun 2024 13:16:23 GMT</pubDate>
            <atom:updated>2024-06-04T13:16:23.750Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iSBzEGJxwgTlkOg9AO1PXQ.jpeg" /></figure><p>Today, I’m excited to share the launch of some new opportunities I’m making available to individuals and companies. I’ve put together a unique set of coaching, mentoring, and consulting options covering several recurring needs I’ve seen in the industry over the last few years. In addition to <a href="https://bluespire.com/p/web-component-engineering">my web standards training</a>, I’m now offering everything from <a href="https://bluespire.com/p/career-coaching">one-on-one career coaching</a> and <a href="https://bluespire.com/p/technical-mentoring">technical mentoring</a> to corporate <a href="https://bluespire.com/p/inner-open-source-program-guidance">OSPO development</a>, and <a href="https://bluespire.com/p/web-architecture-consulting">Web Architecture consulting</a>. Read on to learn more about these options, along with where you or your employer can sign up.</p><p>I look forward to working with you soon!</p><h3>Options for Individuals</h3><p>I have a pretty non-traditional career background. I have no degree in computer science or software engineering, nor have I attended any boot camps or technical schools. I taught myself to program in the 80s but ended up studying music at university. It wasn’t until the early 2000s that I took my first job writing code as a part time member of a small company’s software R&amp;D team.</p><p>It’s been 20 years since then. In that time, I’ve had many challenging, growth experiences, including working as a Principal Architect at Microsoft and working as GEICO’s only Technical Fellow, providing technical leadership to the entire engineering organization, a group consisting of over 4,000 engineers, directors, and VPs.</p><p>I learned a lot of things the hard way. I’ve made my share of mistakes. But I’ve also discovered non-traditional routes that enabled me to innovate and provide unique, high value to the teams and companies I’ve worked for. It would have been a heck of a lot easier with a coach or a mentor…or just someone I could throw ideas at to get feedback. I had this in the earliest days of my career, something I wouldn’t trade for the world, but missed it later on and found that many others were missing this too.</p><p>As a result, I’ve decided to offer a set of services targeted directly at individuals in the tech industry. You may be looking for a job, trying to figure out or advance your career, want to grow technically, or just need a technical “wall” to throw ideas and questions at. Or perhaps you are a manager who wants to find a mentor for a promising individual on your team. Here are a few opportunities you can sign up for today:</p><ul><li><strong>Resume Review </strong>— As a hiring manager and technical interviewer throughout my career, I’ve looked at a lot of resumes. I often find that folks don’t know how to present their career in this form. So, I’m offering a service designed to help. <a href="https://bluespire.com/p/resume-review">The Resume Review service</a> involves a thorough review of your resume, followed by a one-one-one meeting where we’ll discuss what you can do to improve the resume itself as well as your marketability for the future. <a href="https://bluespire.com/p/resume-review">Sign up here.</a></li><li><strong>Mock Technical and General Interviews</strong> — Interviews are a huge source of anxiety for many people. As a musician and public speaker, one of the most important lessons I learned is the importance of practice. It generally results in greater confidence, better performance, and less anxiety. This has led me to offer a Mock Interview service. This is a one-one-one experience that starts with an intense, real-world, 45-minute mock interview and concludes with 15 minutes of feedback. This is a great opportunity to get some practice in, and also learn how you can improve. I’m offering <a href="https://bluespire.com/p/mock-technical-interview-and-retrospective">technical interview</a> as well as <a href="https://bluespire.com/p/mock-general-interview-and-retrospective">general/soft-skills</a> options.</li><li><strong>Job Prep Bundles</strong> — Since sending out resumes and doing interviews often go hand-in-hand, I’ve got a bundle that provides a discount to those who want to combine the Resume Review with either a Mock Technical or General Interview. Sign up for the technical prep bundle <a href="https://bluespire.com/p/technical-job-prep-package">here</a>, or the general bundle <a href="https://bluespire.com/p/general-job-prep-bundle">here</a>.</li><li><strong>Career Coaching </strong>— I’ve had the opportunity to wear a lot of different hats in my career: engineer, architect, manager, PM, consultant, founder, advisor, teacher, etc. I know how difficult it can be figuring out what you want to do, and more so, coming up with a plan that will get you to where you want to be. It’s easy to misjudge opportunities or even oneself along the way. So, I’m offering <a href="https://bluespire.com/p/career-coaching">a Career Coaching opportunity</a>. It’s a subscription that includes two one-on-one meetings per month, dedicated to helping you grow in your career and pursue the best path for you. <a href="https://bluespire.com/p/career-coaching">Subscribe here</a>.</li><li><strong>Technical Mentoring</strong> — Early in my career, I had a close friend who I also became coworkers with. While I had been writing code for many years on my own, I had no experience working in the profession, and was a bit aimless in my learning and growth. This person mentored me, resulting in an amazing work relationship that spurred both of us on in what would become one of the greatest periods of technical growth in my career. Having had that experience, I’ve decided to offer a subscription that includes two one-on-one meetings per month, dedicated to helping people pursue their technical growth and learning. <a href="https://bluespire.com/p/technical-mentoring">You can subscribe here</a>.</li><li><strong>Ask Me Anything</strong> — Sometimes you just need a second opinion. You may be the only dev at your company, or you may be a leader who oversees thousands of people, but rarely has the opportunity for deep review. “Ask Me Anything” is exactly what it sounds like. It’s a way to put some time on my calendar to discuss whatever is on your mind for whatever reason. <a href="https://bluespire.com/p/ask-me-anything">You can schedule time here</a>.</li></ul><p>The above options are all available today. Just follow the links to sign-up, pay, and schedule your meetings. I’m looking forward to getting to know you and helping you along your journey. 😃</p><h3>Options for Teams</h3><p>There are a lot of challenges that engineering organizations and teams have to face today. They tend to be quite different from the challenges that individuals face. So, I’m offering a few unique options targeted directly at teams and engineering decision-makers, based on common areas of struggle and growth I’ve seen over the last several years. I’ve had particular success helping companies in these areas, so I’m now making these services officially available more broadly:</p><ul><li><strong>Outsourced Technical Interview</strong> — Not every company has a large or mature engineering team, making the task of hiring talented individuals all the more challenging. This kind of bootstrapping problem isn’t uncommon. As a result, many companies don’t have the people that can even perform an effective technical interview for the position they are trying to hire for. To help with that, I’m offering a sort of “outsourced” technical interview. If you’ve got a promising candidate, but no one to perform a comprehensive technical interview as part of your hiring process, I can do that part for you. <a href="https://bluespire.com/p/technical-interview">You can schedule the interview here</a>.</li><li><strong>Web Architecture Consulting</strong> — The Web continues to evolve, constantly presenting new possibilities for companies to better serve their customers. The rate of change can make it difficult for leaders to find time to understand how new web technologies apply to them, or how they can be leveraged in a practical way to improve their systems. Those that follow my blog or read my posts on social media won’t be surprised that I’m now officially offering consulting specifically geared towards helping companies better leverage the full power of the web to build the best solutions possible for their customers and business. <a href="https://bluespire.com/p/web-architecture-consulting">You can book a consulting engagement here</a>.</li><li><strong>Inner/Open-Source Program Guidance</strong> — There’s a lot more to open-source than browsing GitHub or installing a package in your project. As companies grow in this area, not only do their needs and challenges expand, but also their potential to do great good, or unfortunately, harm. Building a healthy and responsible open-source program and culture isn’t easy. I’ve helped many companies in these areas over the years, from the basics of understanding how to use open-source, to the challenge of building a successful Open-Source Programs Office (OSPO). Wherever your company is on this journey, there’s probably opportunity for growth and improvement, and I’d be happy to help you explore what’s next. <a href="https://bluespire.com/p/inner-open-source-program-guidance">You can go here to book OSPO consulting</a>.</li><li><strong>Engineering Organization and Culture Strategy</strong> — A healthy culture and an engineering organization that’s effectively structured are two factors that I’ve seen make or break products and whole business units. I’ve had the privilege of working for some amazing leaders who got this right…as well as in some of the most toxic and broken situations. Over the years, I’ve aggregated the best of my experiences and used them to develop a set of values, principles, and practices, designed to help leaders and organizations mature. If you’re struggling in this area, <a href="https://bluespire.com/p/engineering-organization-and-culture-strategy">I’d love to help you work through the challenges before you</a>.</li><li><strong>Fractional Technical Fellow</strong> — For those companies who need a senior technical leader for a longer period of time, <a href="https://bluespire.com/p/fractional-technical-fellow">I’m offering a monthly retainer option</a>. This option would provide a certain number of hours per week, usable for whatever is most pressing. This could be hiring, architecture, open-sourced guidance, cultural strategy, etc. Essentially any of the above, mixed and matched as needed.</li></ul><p>Like the options for individuals, you can follow the links above to immediately book time. However, since many businesses don’t handle purchases in that way, please also feel free to reach out via the official email channel: consulting@bluespire.com</p><h3>More To Come…</h3><p>I’m hopeful that the options I’m launching today will be a help to both individuals and companies who are looking to continue in their professional, technical, or organizational growth. And please keep a lookout for a couple additional opportunities I’ve got coming soon as well, focusing on ways to support both the Web Standards and Open-Source work my company is involved with.</p><p>Cheers!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f272b7f58131" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sharing Styles in Declarative Shadow DOM]]></title>
            <link>https://eisenbergeffect.medium.com/sharing-styles-in-declarative-shadow-dom-c5bf84ffd311?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/c5bf84ffd311</guid>
            <category><![CDATA[html]]></category>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[shadow-dom]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[web-standards]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Tue, 07 May 2024 13:09:52 GMT</pubDate>
            <atom:updated>2024-05-07T13:09:52.679Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*97Ry3wz8AIz_KVpcPphlYg.jpeg" /></figure><p>Recently, <a href="https://medium.com/@eisenbergeffect/using-global-styles-in-shadow-dom-5b80e802e89d">I wrote about how to use global styles in Shadow DOM</a>. It turned out to be relatively simple, using just a tiny bit of JavaScript. In this post, I’d like to focus in on a similar but slightly different scenario, specifically with Declarative Shadow DOM (DSD).</p><h3>The Problem</h3><p>As a reminder, using DSD, we can generate HTML on the server that sets up client-side encapsulation not just for HTML rendering but also for the styles related to that HTML. Here’s a simple “hello world” example:</p><pre>&lt;hello-world&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;&gt;<br>    &lt;style&gt;<br>      :host { display: block; color: red; }<br>    &lt;/style&gt;<br>    Hello World<br>  &lt;/template&gt;<br>&lt;/hello-world&gt;</pre><p>In the above HTML, &lt;hello-world&gt;serves as our DSD &quot;host&quot;. Inside of the host element, we have a &lt;template&gt; element with the shadowrootmode=&quot;open&quot; attribute. <strong>This does not define a template.</strong> This <em>declaratively creates</em> a shadow root that is attached to the host. Then, inside the shadow root, we have a &lt;style&gt; element with some styles that target the host itself. We also have the text &quot;Hello World&quot;.</p><p>What if we want to have three instances of our &lt;hello-world&gt; tag? That would look like this:</p><pre>&lt;hello-world&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;&gt;<br>    &lt;style&gt;<br>      :host { display: block; color: red; }<br>    &lt;/style&gt;<br>    Hello World<br>  &lt;/template&gt;<br>&lt;/hello-world&gt;<br>&lt;hello-world&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;&gt;<br>    &lt;style&gt;<br>      :host { display: block; color: red; }<br>    &lt;/style&gt;<br>    Hello World<br>  &lt;/template&gt;<br>&lt;/hello-world&gt;<br>&lt;hello-world&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;&gt;<br>    &lt;style&gt;<br>      :host { display: block; color: red; }<br>    &lt;/style&gt;<br>    Hello World<br>  &lt;/template&gt;<br>&lt;/hello-world&gt;</pre><p>Now, we can begin to see the problem: <em>duplication</em>. The HTML duplication is something we’re used to. Every server-only HTML solution handles components in this way and has for over two decades. What we’re not used to seeing is the duplicated CSS. Usually, that would be extracted to a stylesheet and then linked. But we can’t do this in DSD without a flash of un-styled content (FOUC). And we don’t want that. While the above duplication is not a big deal, due to its small amount of CSS, we need a solution to this problem for any real-world use of DSD.</p><p>So, how can we declaratively encapsulate our HTML and styles while avoiding both style duplication and FOUC?</p><blockquote><strong>NOTE</strong>: If you are interested in potentially eliminating the duplicate HTML, that’s something I hope will be possible in the future as well, via HTML Modules, Declarative Custom Elements, and other future declarative features.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZUoOxu5g00CIqQ86.png" /><figcaption>Learn Web Component Engineering with veteran UI architect and engineer, Rob Eisenberg.</figcaption></figure><p><em>Interested in learning more about DSD, Web Components, and other related Web Standards? Check out </em><a href="https://bluespire.com/p/web-component-engineering"><em>the Web Component Engineering course</em></a><em> I created, trusted by giants such as Adobe, Microsoft, Progress, and Reddit. Group rates for teams and PPP pricing available upon request.</em></p><h3>The Solution</h3><p>One solution to this problem is to create a custom element, its sole purpose being to facilitate style sharing across DSD boundaries. The element, let’s call it &lt;shared-style&gt;, will take a style-id and use it to look up styles in a local cache. If the styles are there, it will simply add them to the shadow root and remove itself. If the styles are not there, it will find them by id in the current scope, cache them, and then remove itself.</p><p>For this to work, we need a few things:</p><ul><li>The server must be aware of the &lt;shared-style&gt; element when generating HTML (for de-duping).</li><li>Styles must have a unique id associated with them (for de-duping).</li><li>The &lt;shared-style&gt; element must be defined up-front, before any use of DSD (to avoid FOUC).</li></ul><p>We aren’t going to show a server implementation in this post, but one can imagine the server doing something like the following:</p><ul><li>When trying to emit styles, look up whether the styles have already been emitted during the current request.</li></ul><p><strong>If the styles have not been emitted:</strong></p><ul><li>Generate a unique id for the styles.</li><li>Emit a &lt;style id=&quot;unique-id&quot;&gt; element into the DSD for the styles.</li><li>Emit a &lt;shared-style style-id=&quot;unique-id&quot;&gt; element into the DSD, referencing the previously emitted styles.</li><li>Track that the response now includes the styles. (A map from style text to id would do the trick.)</li></ul><p><strong>If the styles have already been emitted:</strong></p><ul><li>Emit a &lt;shared-style style-id=&quot;unique-id&quot;&gt; element into the DSD, referencing the previously emitted styles.</li></ul><p>When the browser receives the HTML, it will look something like this:</p><pre>&lt;hello-world&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;&gt;<br>    &lt;style id=&quot;hello-world&quot;&gt;<br>      :host { display: block; color: red; }<br>    &lt;/style&gt;<br>    &lt;shared-style style-id=&quot;hello-world&quot;&gt;&lt;/shared-style&gt;<br>    Hello World<br>  &lt;/template&gt;<br>&lt;/hello-world&gt;<br><br>&lt;hello-world&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;&gt;<br>    &lt;shared-style style-id=&quot;hello-world&quot;&gt;&lt;/shared-style&gt;<br>    Hello World<br>  &lt;/template&gt;<br>&lt;/hello-world&gt;<br><br>&lt;hello-world&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;&gt;<br>    &lt;shared-style style-id=&quot;hello-world&quot;&gt;&lt;/shared-style&gt;<br>    Hello World<br>  &lt;/template&gt;<br>&lt;/hello-world&gt;</pre><p>Notice that the styles only appear in the markup the first time they are needed. Everywhere else references them via their unique id instead.</p><blockquote><strong>IMPORTANT</strong>: An alternative solution to the above would be to try to side-step the problem altogether. For example, enabling <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Compression">HTTP Compression</a> would solve the issue of increased payload due to duplicate styles. You’ll want to test both approaches with your own application before making a decision about which one or combination to use.</blockquote><h3>The Implementation</h3><p>With the high-level solution worked out, let’s look at an implementation for &lt;shared-style&gt;:</p><pre>const lookup = new Map();<br><br>class SharedStyle extends HTMLElement {<br>  connectedCallback() {<br>    const id = this.getAttribute(&quot;style-id&quot;);<br>    const root = this.getRootNode();<br>    let styles = lookup.get(id);<br><br>    if (styles) {<br>      root.adoptedStyleSheets.push(styles);<br>    } else {<br>      styles = new CSSStyleSheet();<br>      const element = root.getElementById(id);<br>      styles.replaceSync(element.innerHTML);<br>      lookup.set(id, styles);<br>    }<br><br>    this.remove();<br>  }<br>}<br><br>customElements.define(&quot;shared-style&quot;, SharedStyle);</pre><p>The &lt;shared-style&gt; element will have its connectedCallback() invoked by the browser as it streams the HTML into the DSD. At this point, our element will read its style-id attribute and use it to look up the styles in its cache.</p><p><strong>If the cache already has an entry for the id:</strong></p><ul><li>The element adds the styles to the adoptedStyleSheets collection of the containing shadow root.</li><li>Then, the &lt;shared-style&gt; removes itself from the DSD.</li></ul><p><strong>If the styles are not present in the cache:</strong></p><ul><li>First, the element constructs a CSSStyleSheet instance.</li><li>Second, it locates the &lt;style&gt; element inside the containing DSD using the id.</li><li>Third, the &lt;style&gt; element&#39;s contents are used to provide the styles for the CSSStyleSheet.</li><li>Fourth, the style sheet is cached.</li><li>And finally, the &lt;shared-style&gt; element removes itself from the DSD.</li></ul><p>There are a couple other details of the implementation worth pointing out:</p><ul><li>We use this.getRootNode() to find the shadow root that the &lt;shared-style&gt; element is inside of. If it’s not inside of a shadow root, this API will return the document, which also has an adoptedStyleSheets collection.</li><li>If it is the first time &lt;shared-style&gt; is seeing a particular style-id, it doesn&#39;t need to push the styles into the adoptedStyleSheets of the root because an in-line &lt;style&gt; element is already present, fulfilling the same purpose.</li></ul><p>That’s all there is to it. Don’t forget to include the small bit of JS above inline before any use of &lt;shared-style&gt; and you&#39;ll be able to share any number of styles across shadow root boundaries, with full support for DSD streaming and no FOUC.</p><h3>Wrapping Up</h3><p>Ideally, we’d be able to share styles across DSDs without any JavaScript. In fact, <a href="https://github.com/WICG/webcomponents/issues/939">there is a proposal for a fully declarative way of doing this</a>. But in the meantime, we can solve these types of issues ourselves, using our knowledge of how DSD works and filling in the gaps with a very small amount of code.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c5bf84ffd311" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A TC39 Proposal for Signals]]></title>
            <link>https://eisenbergeffect.medium.com/a-tc39-proposal-for-signals-f0bedd37a335?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/f0bedd37a335</guid>
            <category><![CDATA[reactivity]]></category>
            <category><![CDATA[framework]]></category>
            <category><![CDATA[tc39]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-platform]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Mon, 01 Apr 2024 13:33:03 GMT</pubDate>
            <atom:updated>2024-04-20T16:16:53.316Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1Mz0nsI0CWe3dVl1NgVniw.png" /></figure><p>In August of last year, <a href="https://eisenbergeffect.medium.com/the-future-of-native-html-templating-and-data-binding-5f3e52fda259">I mentioned that I wanted to begin pursuing a potential standard for <em>signals </em>in TC39</a>. Today, I’m happy to share that <a href="https://github.com/proposal-signals/proposal-signals">a v0 draft of such a proposal</a> is publicly available, along with <a href="https://github.com/proposal-signals/proposal-signals/tree/main/packages/signal-polyfill">a spec-compliant polyfill</a>.</p><h3>What are signals?</h3><p>A signal is a data type that enables one-way data flow by modeling cells of state and computations derived from other state/computations. The state and computations form an acyclic graph, where each node has other nodes that derive state from its value (sinks) and/or that contribute state to its value (sources). A node may also be tracked as “clean” or “dirty”.</p><p>But what does all that mean? Let’s look at a simple example.</p><p>Imagine we have a <em>counter </em>we want to track. We can represent that as state:</p><pre>const counter = new Signal.State(0);</pre><p>We can read the current value with get():</p><pre>console.log(counter.get()); // 0</pre><p>And we can change current value with set():</p><pre>counter.set(1);<br>console.log(counter.get()); // 1</pre><p>Now, let’s imagine that we want to have another signal that indicates whether our counter holds an even number or not.</p><pre>const isEven = new Signal.Computed(() =&gt; (counter.get() &amp; 1) == 0);</pre><p>Computations aren’t writable, but we can always read their latest value:</p><pre>console.log(isEven.get()); // false<br>counter.set(2);<br>console.log(isEven.get()); // true</pre><p>In the above example, isEven has counter as a <em>source </em>and counter has isEven as a <em>sink.</em></p><p>We can add another computation that provides the parity of our counter:</p><pre>const parity = new Signal.Computed(() =&gt; isEven.get() ? &quot;even&quot; : &quot;odd&quot;);</pre><p>Now we have parity with isEven as a <em>source </em>and isEven with parity as a <em>sink (</em>remember that<em> </em>isEven already has<em> </em>counter as a <em>source)</em>. As a result, we can change the original counter and the state will flow unidirectionally to parity.</p><pre>counter.set(3);<br>console.log(parity.get()); // odd</pre><p>Everything we’ve done so far seems like it could be done through normal function composition. But if implemented that way, without signals, there would be no source/sink graph behind the scenes. So, why do we want that graph? What is it doing for us?</p><p>Recall that I mentioned signals can be “clean” or “dirty”. When we change the value of counter, it becomes <em>dirty</em>. Because we have a graph relationship, we can then mark all the sinks of counter (potentially) dirty as well, and all their sinks, and so on and so forth.</p><p><strong>There’s an important detail to understand here.</strong> The signal algorithm is not a <em>push </em>model. Making a change to counter does not eagerly push out an update to the value of isEven and then via the graph, an update to parity. It is also not a pure <em>pull </em>model. Reading the value of parity doesn’t always compute the value of parity or isEven. Rather, when counter changes, it pushes <em>only the change in the dirty flag </em>through the graph. Any potential re-computation is delayed until a specific signal’s value is <em>explicitly pulled.</em></p><p><strong>We call this a “push then pull” model. </strong>Dirty flags are eagerly updated (pushed) while computations are lazily evaluated (pulled).</p><p>There are a number of advantages that arise out of combining an acyclic graph data structure with a “push then pull” algorithm. Here are a few:</p><ul><li>Signal.Computed is automatically memoized. If the source values haven’t changed, then there’s no need to re-compute.</li><li>Unneeded values aren’t re-computed even when sources change. If a computation is dirty but nothing reads its value, then no re-computation occurs.</li><li>False or “over updating” can be avoided. For example, if we change counter from 2 to 4, yes, it is dirty. But when we pull the value of parity its computation will not need to re-run, because isEven, once pulled, will return the same result for 4 as it did for 2.</li><li>We can be notified when signals become dirty and choose how to react.</li></ul><p>These characteristics turn out to be very important when efficiently updating user interfaces. To see how, we can introduce a fictional effect function that will invoke some action when one of its sources becomes dirty. For example, we could update a text node in the DOM with the parity:</p><pre><br>effect(() =&gt; node.textContent = parity.get());<br>// The effect&#39;s callback is run and the node&#39;s text is updated with &quot;odd&quot;<br>// The effect watches the callback&#39;s source (parity) for dirty changes.<br><br>counter.set(2);<br>// The counter dirties its sinks, resulting in the effect being<br>// marked as potentially dirty, so a &quot;pull&quot; is scheduled.<br>// The scheduler begins to re-evaluate the effect callback by pulling parity.<br>// parity begins to evaluate by pulling isEven.<br>// isEven pulls counter, resulting in a changed value for isEven.<br>// Because isEven has changed, parity must be re-computed.<br>// Because parity has changed, the effect runs and the text updates to &quot;even&quot;<br><br>counter.set(4);<br>// The counter dirties its sinks, resulting in the effect being<br>// marked as potentially dirty, so a &quot;pull&quot; is scheduled.<br>// The scheduler begins to re-evaluate the effect callback by pulling parity.<br>// parity begins to evaluate by pulling isEven.<br>// isEven pulls counter, resulting in the same value for isEven as before.<br>// isEven is marked clean.<br>// Because isEven is clean, parity is marked clean.<br>// Because parity is clean, the effect doesn&#39;t run and the text is unaffected.</pre><p>Hopefully this brings some clarity to what a signal is, and an understanding of the significance of the combination of the acyclic source/sink graph with its “push then pull” algorithm.</p><h3>Who has been working on this?</h3><p>Late in 2023 I partnered with <a href="https://x.com/littledan?s=20">Daniel Ehrenberg</a>, <a href="https://twitter.com/benlesh">Ben Lesh</a>, and <a href="https://twitter.com/trueadm">Dominic Gannaway</a> to try to round up as many signal library authors and maintainers of front-end frameworks as we could. Anyone who expressed an interest was invited to help us begin to explore the feasibility of signals as a standard.</p><p>We started with a survey of questions and one-on-one interviews, looking for common themes, ideas, use cases, semantics, etc. We didn’t know whether there was even a common model to be found. To our delight, we discovered that there was quite a bit of agreement from the start.</p><p>Over the last 6–7 months, detail after detail was poured over, attempting to move from general agreement to the specifics of data structures, algorithms, and an initial API. You may recognize a number of the libraries and frameworks that have provided design input at various times throughout the process so far: <a href="https://angular.io/">Angular</a>, <a href="https://bubble.io/">Bubble</a>, <a href="https://emberjs.com/">Ember</a>, <a href="https://www.fast.design/">FAST</a>, <a href="https://mobx.js.org/">MobX</a>, <a href="https://preactjs.com/">Preact</a>, <a href="https://qwik.dev/">Qwik</a>, <a href="https://rxjs.dev/">RxJS</a>, <a href="https://www.solidjs.com/">Solid</a>, <a href="https://www.starbeamjs.com/">Starbeam</a>, <a href="https://svelte.dev/">Svelte</a>, <a href="https://vuejs.org/">Vue</a>, <a href="https://blog.angular.io/angular-and-wiz-are-better-together-91e633d8cd5a">Wiz</a>, and more…</p><p>It’s quite a list! And I can honestly say, looking back at my own work in Web Standards over the last ten years, this is one of the most amazing collaborations I’ve had the honor to be a part of. It’s a truly special group of people with exactly the type of collective experience that we need to continue to move the web forward.</p><blockquote><strong>IMPORTANT</strong>: If we missed your library or framework, there’s still plenty of opportunity to get involved! Nothing is set in stone. We’re still at the beginning of this process. Scroll down to the section titled “How can I get involved in the proposal?” to learn more.</blockquote><h3>What is in the signals proposal?</h3><p>The proposal, <a href="https://github.com/proposal-signals/proposal-signals">which can be found on GitHub</a>, includes:</p><ul><li>Background, motivation, design goals, and an FAQ.</li><li>A proposed API for creating both state and computed signals.</li><li>A proposed API for watching signals.</li><li>Various additional proposed utility APIs, such as for introspection.</li><li>A detailed description of the various signal algorithms.</li><li>A spec-compliant polyfill covering all the proposed APIs.</li></ul><p>The signals proposal does not include an effect API, since such APIs are often deeply integrated with rendering and batch strategies that are highly framework/library dependent. However, the proposal does seek to define a set of primitives and utilities that library authors can use to implement their own effects.</p><p>On that note, the proposal is designed in such a way as to recognize that there are two broad categories of signal users:</p><ul><li>Application developers</li><li>Library/framework/infrastructure developers</li></ul><p>APIs that are intended to be used by application developers are exposed directly from the Signal namespace. These include Signal.State() and Signal.Computed(). APIs which should rarely if ever be used in application code, and more likely involve subtle handling, typically at the infrastructure layer, are exposed through the Signal.subtle namespace. These include Signal.subtle.Watcher , Signal.subtle.untrack(), and the introspection APIs.</p><blockquote><strong>ASIDE</strong>: Haven’t seen something like the idea of the subtle namespace in JavaScript before? <a href="https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle">Checkout Crypto.subtle</a>.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fQEpI7NerpE_a5kJbc4gcQ.png" /><figcaption>Learn Web Component Engineering with veteran UI architect and engineer, Rob Eisenberg.</figcaption></figure><p><em>We interrupt this blog post on signals to remind you of </em><a href="https://bluespire.com/p/web-component-engineering"><em>the Web Component Engineering course</em></a><em>, trusted by giants such as Adobe, Microsoft, Progress, and Reddit. If you want to learn modern UI engineering, get up to speed on dozens of Web Standards, and even see how signals can be integrated with Web Components, this is the course for you. Group rates for teams and PPP pricing available upon request.</em></p><p><em>Now, back to your regularly scheduled program…</em></p><h3>As an app dev, how do I use signals?</h3><p>Many of today’s popular component and rendering frameworks are already using signals. Over the coming months, we hope that framework maintainers will experiment with re-platforming their systems on top of the signals proposal, providing feedback along the way, and helping us prove out whether it is possible to leverage a potential signals standard.</p><p>If this were to work out, many app developers would use signals through their chosen component framework; their patterns wouldn’t change. However, their framework would then be more interoperable (reactive data interoperability), smaller (signals are built in and don’t need to ship as JS), and hopefully faster (native signals as part of the JS runtime).</p><p>Library authors would then be able to write code using signals that works natively with any component or rendering library that understands the standard, reducing the fragmentation in the web ecosystem. Application developers would be able to build model/state layers that are decoupled from their current rendering technology, giving them more architectural flexibility and the ability to experiment with and evolve their view layer without re-writing their entire application.</p><p>So, let’s say you are a developer that wants to create a library using signals, or who wants to build an application state layer on these primitives. What would that code look like?</p><p>Well, we’ve seen a bit of it already, when I explained the basics of signals through the Signal.State() and Signal.Computed() APIs above. These are the two primary APIs that an application developer would use, if not using them indirectly through a framework’s API. They can be used by themselves to represent stand-alone reactive state and computations or in combination with other JavaScript constructs, such as classes. Here’s a Counter class that uses a signal to represent its internal state:</p><pre>export class Counter {<br>  #value = new Signal.State(0);<br><br>  get value() {<br>    return this.#value.get();<br>  }<br><br>  increment() {<br>    this.#value.set(this.#value.get() + 1);<br>  }<br><br>  decrement() {<br>    if (this.#value.get() &gt; 0) {<br>      this.#value.set(this.#value.get() - 1);<br>    }<br>  }<br>}<br><br>const c = new Counter();<br>c.increment();<br>console.log(c.value);</pre><p>One particularly nice way to use signals is in combination with decorators. We can create a @signal decorator that turns an accessor into a signal as follows:</p><pre>export function signal(target) {<br>  const { get } = target;<br><br>  return {<br>    get() {<br>      return get.call(this).get();<br>    },<br><br>    set(value) {<br>      get.call(this).set(value);<br>    },<br>    <br>    init(value) {<br>      return new Signal.State(value);<br>    },<br>  };<br>}</pre><p>Then we can use it to reduce boilerplate and improve readability of our Counter class like this:</p><pre>export class Counter {<br>  @signal accessor #value = 0;<br><br>  get value() {<br>    return this.#value;<br>  }<br><br>  increment() {<br>    this.#value++;<br>  }<br><br>  decrement() {<br>    if (this.#value &gt; 0) {<br>      this.#value--;<br>    }<br>  }<br>}</pre><p>There are many more ways to use signals, but hopefully these examples provide a good starting point for those who want to experiment at this state.</p><blockquote><strong>ASIDE</strong>: Some users of particular signal libraries may not approve of my use of the following code above this.#value.set(this.#value.get() + 1). In some signal implementations, this can cause an infinite loop when used within a computed or an effect. This does not cause a problem in the current proposal’s computed, nor does it cause a problem in the example effect demonstrated below. Should it cause a loop though? Or should it throw? What should be the behavior? This is an example of the many types of details that need to be worked through in order to standardize an API like this.</blockquote><h3>As a library/infra dev, how do I integrate signals?</h3><p>We hope that maintainers of view and component libraries will experiment with integrating this proposal, as well as those who create state management and data-related libraries. A first integration step would be to update the library’s signals to use Signal.State() and Signal.Computed() internally instead of their current library-specific implementation. Of course this isn’t enough. A common next step would be to update any effect or equivalent infrastructure. As I mentioned above, the proposal doesn’t provide an effect implementation. Our research showed that this was too connected to the details of rendering and batching to standardize at this point. Rather, the Signal.subtle namespace provides the primitives that a framework can use to build its own effects. Let’s take a look at the implementation of a simple effect function that batches updates on the microtask queue.</p><pre>let needsEnqueue = true;<br><br>const w = new Signal.subtle.Watcher(() =&gt; {<br>  if (needsEnqueue) {<br>    needsEnqueue = false;<br>    queueMicrotask(processPending);<br>  }<br>});<br><br>function processPending() {<br>  needsEnqueue = true;<br>    <br>  for (const s of w.getPending()) {<br>    s.get();<br>  }<br><br>  w.watch();<br>}<br><br>export function effect(callback) {<br>  let cleanup;<br>  <br>  const computed = new Signal.Computed(() =&gt; {<br>    typeof cleanup === &quot;function&quot; &amp;&amp; cleanup();<br>    cleanup = callback();<br>  });<br>  <br>  w.watch(computed);<br>  computed.get();<br>  <br>  return () =&gt; {<br>    w.unwatch(computed);<br>    typeof cleanup === &quot;function&quot; &amp;&amp; cleanup();<br>  };<br>}</pre><p>The effect function begins by creating a Signal.Computed() out of the user-provided callback. It can then use the Signal.subtle.Watcher to watch the computed’s sources. To enable the watcher to “see” the sources, we need to execute the computed at least once, which we do by calling get(). You may also notice that our effect implementation supports a basic mechanism for callbacks to provide cleanup functions as well as a way to stop watching, via the returned function.</p><p>Looking at the creation of the Signal.subtle.Watcher, the constructor takes a callback that will be invoked synchronously whenever any of its watched signals becomes dirty. Because a Watcher can watch any number of signals, we schedule processing of all dirty signals on the microtask queue. Some basic guard logic ensures that scheduling only happens once, until the pending signals are handled.</p><p>In the processPending() function, we loop over all the signals that the watcher has tracked as pending and re-evaluate them by calling get(). We then ask the watcher to resume watching all its tracked signals again.</p><p>That’s the basics. Most frameworks will handle queuing in a way that’s integrated with their rendering or component system, and they’ll likely make other implementation changes in order to support the working model of their system.</p><h4>Other APIs</h4><p>Another API that’s likely to be used in infrastructure is the Signal.subtle.untrack() helper. This function takes a callback to execute and ensures that signals read within the callback will not be tracked.</p><p>I feel the need to remind readers: the Signal.subtle namespace designates APIs that should be used with care, and mostly by framework or infrastructure authors. Using something like Signal.subtle.untrack() incorrectly or carelessly can mess up your application in ways that are difficult to track down.</p><p>With that said, let’s look at a legitimate use of this API.</p><p>Many view frameworks have a way to render a list of items. Typically, you pass the framework an array and a “template” or fragment of HTML that it should render for each item in the array. As an application developer, you want any interaction with that array to be tracked by the reactivity system so that your list’s rendered output will stay in sync with your data. But what about the framework itself? It must access the array in order to render it. If the framework’s access of the array were tracked by the dependency system, that would create all sorts of unnecessary connections in the graph, leading to false or over updating…not to mention the likelihood of performance problems and strange bugs. The Signal.subtle.untrack() API provides the library author with a simple way to handle this challenge. As an example, let’s look at a small bit of code from <a href="https://www.solidjs.com/">SolidJS</a> that renders arrays, which I’ve slightly modified to use the proposed standard. We won’t look at the whole implementation. I’ve cut most of the code out for simplicity. Hopefully, looking at the high-level outline will help explain the use case.</p><pre>export function mapArray(list, mapFn, options = {}) {<br>  let items = [],<br>      mapped = [],<br>      len = 0,<br>    /* ...other variables elided... */;<br><br>  // ...elided...<br><br>  return () =&gt; {<br>    let newItems = list() || [], // Accessing the array gets tracked<br>        i,<br>        j;<br>    <br>    // Accessing the length gets tracked<br>    let newLen = newItems.length;<br><br>    // Nothing in the following callback will be tracked. We don&#39;t want our<br>    // framework&#39;s rendering work to affect the signal graph!<br>    return Signal.subtle.untrack(() =&gt; {<br>      let newIndices,<br>          /* ...other variables elided... */;<br><br>      // fast path for empty arrays<br>      if (newLen === 0) {<br>        // ... read from the array; not tracked ...<br>      }<br>      // fast path for new create<br>      else if (len === 0) {<br>        // ... read from the array; not tracked ...<br>      } else {<br>        // ... read from the array; not tracked ...<br>      }<br><br>      return mapped;<br>    });<br>  };<br>}</pre><p>Even though I’ve elided the bulk of Solid’s algorithm, you can see how the main body of work is done within an untracked block of code that accesses the array.</p><p>There are additional APIs within the Signal.subtle namespace which you can explore at your leisure. Hopefully, the above examples help to demonstrate the kinds of scenarios this part of the proposal is designed for.</p><h3>How can I get involved in the proposal?</h3><p>Just jump in! <a href="https://github.com/proposal-signals/proposal-signals">Everything is on GitHub</a>. You’ll find the proposal in the root of the repo, and the polyfill in the packages folder. You can also <a href="https://discord.gg/5pguaYDvsb">chat with us on Discord</a>.</p><p>Here are a few ideas for how you can get started contributing:</p><ul><li>Try out signals within your framework or application.</li><li>Improve the documentation/learning materials for signals.</li><li>Document use cases (whether it’s something the API supports well or not).</li><li>Write more tests, e.g., by porting them from other signal implementations.</li><li>Port other signal implementations to this API.</li><li>Write benchmarks for signals, both synthetic and real-world application ones.</li><li>File issues on polyfill bugs, your design thoughts, etc.</li><li>Try developing reactive data structures/state management abstractions on top of signals.</li><li>Implement signals natively in a JS engine (behind a flag/in a PR, not shipped!)</li></ul><p>There are also a variety of issues that have already been created to track ongoing areas of debate, potential modifications to algorithms, additional APIs, use cases, polyfill bugs, etc. Please take a look at those and see which ones you can provide insights on. If you are a framework or library author, we really want you to help us understand any scenarios or use cases that you think would pose a challenge for the current proposal.</p><h3>What’s next?</h3><p>We’re still at the beginning of this effort. In the next few weeks <a href="https://x.com/littledan?s=20">Daniel Ehrenberg</a> (Bloomberg) and <a href="https://twitter.com/JatinRamanathan">Jatin Ramanathan</a> (Google/Wiz) will bring the proposal before TC39, seeking Stage 1. Stage 1 means that that proposal is under consideration. Right now, we’re not even there yet. You can think of signals as being at Stage 0. <a href="https://tc39.es/process-document/">The earliest of the early.</a> After presenting at TC39, we’ll continue to evolve the proposal based on feedback from that meeting, and in line with what we hear from folks who get involved through GitHub.</p><p>Our approach is to take things slow and to prove ideas out through prototyping. We want to make sure that we don’t standardize something that no one can use. We’ll need your help to achieve this. With your contributions as described above, we believe we’ll be able to refine this proposal and make it suitable for all.</p><p>I believe a signal standard has tremendous potential for JavaScript and for the Web. It’s been exciting to work with such a great set of folks from the industry who are deeply invested in reactive systems. Here’s to more future collaboration and a better web for all. 🎊</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f0bedd37a335" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Global Styles in Shadow DOM]]></title>
            <link>https://eisenbergeffect.medium.com/using-global-styles-in-shadow-dom-5b80e802e89d?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/5b80e802e89d</guid>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[html]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[shadow-dom]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Wed, 31 Jan 2024 14:16:57 GMT</pubDate>
            <atom:updated>2024-01-31T14:16:57.914Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-bOyW7Sooc1l6iVfD_TcRw.jpeg" /></figure><p>One of the most common misunderstandings folks seem to have about Web Components is that they can’t take advantage of global CSS. <strong>This is simply not true. </strong>With only a few lines of JavaScript you can enable any Web Component to respond to your global CSS. In this article, I’ll show you how to build this into your own components, as well as how to take pre-existing <a href="https://www.fast.design/">FAST</a> and <a href="https://lit.dev/">Lit</a> components and modify them to also respond to global styles. We’ll even see how to accomplish this with Declarative Shadow DOM (DSD).</p><p>Shadow DOM, by design, provides encapsulation of styles. This means that only the styles that you explicitly add to your shadow root will affect its presentation. Likewise, only the styles that you explicitly leak out will affect external DOM. One only needs to leverage a couple of standard HTML features to import global styles into a shadow root. Let’s see how this works…</p><h3>Using Global Styles in a VanillaJS Web Component</h3><p>Let’s begin by looking at some HTML containing global styles and two web components, each of which contains the same h1 and h2 structure as the body.</p><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>  &lt;head&gt;<br>    &lt;meta charset=&quot;utf-8&quot;&gt;<br>    &lt;title&gt;Global Styles in Shadow DOM&lt;/title&gt;<br>    &lt;link href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css&quot; rel=&quot;stylesheet&quot; integrity=&quot;sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN&quot; crossorigin=&quot;anonymous&quot;&gt;<br>    &lt;style&gt;<br>      h2 {<br>        color: red;<br>      }<br>    &lt;/style&gt;  <br>  &lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;h1&gt;Light DOM H1 with Bootstrap Styles&lt;/h1&gt;<br>    &lt;h2&gt;Light DOM H2 with Bootstrap and Global Custom Styles&lt;/h2&gt;<br>    &lt;hr&gt;<br>    <br>    &lt;not-using-global-styles&gt;&lt;/not-using-global-styles&gt;<br>    &lt;hr&gt;<br>    <br>    &lt;using-global-styles&gt;&lt;/using-global-styles&gt;<br>    &lt;hr&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;</pre><p>In the above HTML, I have a standard CSS link to <a href="https://getbootstrap.com/">Bootstrap</a>. I also have some custom styles provided via a style element. Here’s how the page renders.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iQ1HLdmdU1eOjLOgL6gsHQ.png" /><figcaption>Global styles affecting Web Components with Shadow DOM.</figcaption></figure><p>The h1 and h2 that are directly in the body pick up both Bootstrap and the custom styles. The h1 and h2 that are in the not-using-global-styles custom element do not pick up the global CSS. Both of these cases are as expected. But the h1 and h2 that are in the using-global-styles <em>do </em>pick up <em>both </em>Bootstrap and the custom styles.</p><p><strong>How is this accomplished?</strong></p><p>If we compare the implementation of not-using-global-styles to that of using-global-styles, we’ll see one important difference.</p><p><strong>not-using-global-styles.js</strong></p><pre>export class NotUsingGlobalStyles extends HTMLElement {<br>  constructor() {<br>    super();<br><br>    this.attachShadow({ mode: &quot;open&quot; }).innerHTML = `<br>      &lt;h1&gt;Shadow DOM H1 without Bootstrap Styles&lt;/h1&gt;<br>      &lt;h2&gt;Shadow DOM H2 without Bootstrap or Global Custom Styles&lt;/h2&gt;<br>    `;<br>  }<br>}<br><br>customElements.define(&quot;not-using-global-styles&quot;, NotUsingGlobalStyles);</pre><p><strong>using-global-styles.js</strong></p><pre>import { addGlobalStylesToShadowRoot } from &quot;./global-styles.js&quot;;<br><br>export class UsingGlobalStyles extends HTMLElement {<br>  constructor() {<br>    super();<br><br>    this.attachShadow({ mode: &quot;open&quot; }).innerHTML = `<br>      &lt;h1&gt;Shadow DOM H1 with Bootstrap Styles&lt;/h1&gt;<br>      &lt;h2&gt;Shadow DOM H2 with Bootstrap and Global Custom Styles&lt;/h2&gt;<br>    `;<br><br>    addGlobalStylesToShadowRoot(this.shadowRoot); // look here!<br>  }<br>}<br><br>customElements.define(&quot;using-global-styles&quot;, UsingGlobalStyles);</pre><p>The magic is in a single helper function that I’ve written: addGlobalStylesToShadowRoot. All you need to do to inherit global CSS in your Shadow DOM is import that function and call it with your shadow root. Here’s how it’s implemented:</p><p><strong>global-styles.js</strong></p><pre>let globalSheets = null;<br><br>export function getGlobalStyleSheets() {<br>  if (globalSheets === null) {<br>    globalSheets = Array.from(document.styleSheets)<br>      .map(x =&gt; {<br>        const sheet = new CSSStyleSheet();<br>        const css = Array.from(x.cssRules).map(rule =&gt; rule.cssText).join(&#39; &#39;);<br>        sheet.replaceSync(css);<br>        return sheet;<br>      });<br>  }<br><br>  return globalSheets;<br>}<br><br>export function addGlobalStylesToShadowRoot(shadowRoot) {<br>  shadowRoot.adoptedStyleSheets.push(<br>    ...getGlobalStyleSheets()<br>  );<br>}</pre><p>By leveraging CSSStyleSheet and the shadow root’s adoptedStyleSheets array, we can add any style sheet we want to our Shadow DOM, global or otherwise. In the code above, I’ve written a function called getGlobalStyleSheets() that looks at the document.styleSheets collection and creates CSSStyleSheet instances using standard APIs. The function lazily constructs the sheets and then caches them, so that the work is only done when needed and all sheet instances can be shared across all Shadow DOMs that need them.</p><blockquote><strong>NOTE</strong>: The style sheets we get from document.styleSheets are not “Constructible Style Sheet” instances, so we can’t directly use them with adoptedStyleSheets. No problem. We just create our own constructible style sheets from the css text of the document sheets.</blockquote><p>Really, that’s all there is to it. Just a few lines of JavaScript using standard APIs will make global styles available in any Web Component where you need them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fQEpI7NerpE_a5kJbc4gcQ.png" /><figcaption>Learn Web Component Engineering with veteran UI architect and engineer, Rob Eisenberg.</figcaption></figure><p><em>Want to know more about </em><em>CSSStyleSheet, </em><em>adoptedStyleSheets, Declarative Shadow DOM, and other Shadow DOM and styling topics discussed in this blog? </em><a href="https://bluespire.com/p/web-component-engineering"><em>Check out my Web Component Engineering course.</em></a><em> Group rates for teams and PPP pricing available upon request.</em></p><h3>Adding Global Styles to 3rd Party Web Components</h3><p>The code above seems fine if you are the author of the Web Component. But what if you need to use global styles with a component created by someone else who used a Web Component Library like <a href="https://www.fast.design/\">FAST</a> or <a href="https://lit.dev/">Lit</a>? You could use the addGlobalStylesToShadowRoot() helper function on each instance’s shadow root (assuming it’s open). That’s not very practical though. Is there a way that you could control this for all instances of a component type?</p><p><strong>Yes. </strong>Let’s take a look at each library to see how.</p><h4>Adding Global Styles to an Existing FAST Element</h4><p>Let’s start by looking at some very basic code for a FASTElement that doesn’t use global styles. Imagine this was written by another team, or that it was part of an open-source component library you are using.</p><pre>import { FASTElement, FASTElementDefinition, html } from &quot;@microsoft/fast-element&quot;;<br><br>export const template = html`<br>  &lt;h1&gt;Existing FAST Element Shadow DOM H1&lt;/h1&gt;<br>  &lt;h2&gt;Existing FAST Element Shadow DOM H2&lt;/h2&gt;<br>`;<br><br>export class ExistingFASTElement extends FASTElement {<br><br>}<br><br>export const definition = new FASTElementDefinition(ExistingFASTElement, {<br>  name: &quot;existing-fast-element&quot;,<br>  template<br>});</pre><p>In FAST, all Web Components have a FASTElementDefinition that provides metadata about the component, used when constructing instances. 3rd-party component libraries built on FAST usually provide these definitions, decoupled from component registration. As a result, we can easily add global styles to all instances of a Web Component type using a small amount of code that modifies the definition itself. Here’s how we do it:</p><pre>import { ElementStyles } from &quot;@microsoft/fast-element&quot;;<br>import { definition } from &quot;./existing-fast-element.js&quot;;<br>import { getGlobalStyleSheets } from &quot;./global-styles.js&quot;;<br><br>// FAST<br>definition.styles = ElementStyles.create(<br>  getGlobalStyleSheets(), <br>  definition.styles<br>);<br><br>definition.define();</pre><p>FAST provides an ElementStyles helper for working with style sheets. We can simply pass our global styles, provided by getGlobalStyleSheets(), along with any existing styles defined by the component. This will then return a new set of styles associated with the definition that will be used by all instances of the component.</p><blockquote><strong>NOTE</strong>: Be mindful of the ordering of the style sheets. In the above example, we have ordered the sheets so that the global styles come before the component’s built-in styles. If that’s not desired, you can always swap the order.</blockquote><h4>Adding Global Styles to an Existing Lit Element</h4><p>Similarly, we can look at some very basic code for a LitElement that doesn’t use global styles.</p><pre>import { LitElement, html } from &quot;lit&quot;;<br><br>export class ExistingLitElement extends LitElement {<br>  render() {<br>    return html`<br>      &lt;h1&gt;Existing Lit Element Shadow DOM H1&lt;/h1&gt;<br>      &lt;h2&gt;Existing Lit Element Shadow DOM H2&lt;/h2&gt;<br>    `;<br>  }<br>}</pre><p>Lit looks for styles in a static styles field on the class. So, all we need to do is alter that to add the global styles. Here’s what that looks like:</p><pre>import { ExistingLitElement } from &quot;./existing-lit-element.js&quot;;<br>import { getGlobalStyleSheets } from &quot;./global-styles.js&quot;;<br><br>// Lit<br>ExistingLitElement.styles = [<br>  ...getGlobalStyleSheets(),<br>  ...(Array.isArray(ExistingLitElement.styles)<br>      ? ExistingLitElement.styles <br>        : ExistingLitElement.styles ? [ExistingLitElement.styles] : [])<br>];<br><br>customElements.define(&quot;existing-lit-element&quot;, ExistingLitElement);</pre><p>This looks slightly more complicated, mainly because we need to do a bit more work to ensure that we’re handling any existing styles on the 3rd-party element. So, just a bit of conditional logic checking for undefined, single, and array possibilities is needed. But the concept is the same as with FAST. We just get the global styles and merge them with any existing styles, then assign that back to the styles static field. <em>Done.</em></p><h3>Adding Global Styles to Declarative Shadow DOM (DSD)</h3><p>What if we want to enable global styles to work in a Declarative Shadow DOM (DSD)? How would we accomplish that?</p><p>Well, we’re going to use the same helper, but we’re going to wrap it in a very simple custom element that will add the global styles while the DSD is being streamed into the DOM. Here’s the small element that does the work:</p><p><strong>adopt-global-styles.js</strong></p><pre>import { addGlobalStylesToShadowRoot } from &quot;./global-styles.js&quot;;<br><br>export class AdoptGlobalStyles extends HTMLElement {<br>  connectedCallback() {<br>    addGlobalStylesToShadowRoot(this.getRootNode());<br>    this.remove();<br>  }<br>}<br><br>customElements.define(&quot;adopt-global-styles&quot;, AdoptGlobalStyles);</pre><p>And here’s how we use it in DSD:</p><pre>&lt;dsd-element&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;&gt;<br>    &lt;adopt-global-styles&gt;&lt;/adopt-global-styles&gt;<br>    &lt;h1&gt;DSD H1 with Bootstrap Styles&lt;/h1&gt;<br>    &lt;h2&gt;DSD H2 with Bootstrap and Global Custom Styles&lt;/h2&gt;<br>  &lt;/template&gt;<br>&lt;/dsd-element&gt;</pre><p>The adopt-global-styles element uses getRootNode() to obtain the shadow root that it is inside of. In this case, the shadow root created by DSD. It then adds the global styles to that root and removes itself once the work is done.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VwcF_zUIwRDO_LNaklKMHA.png" /><figcaption>All Light and Shadow DOM examples.</figcaption></figure><h3>Standards, Experiments, and Helper Libraries</h3><p>While it’s possible to use global styles in your Shadow DOM, many would like a built-in standard enabling this, so no JavaScript is required. To that end, various conversations have been happening, attempting to hash out what the key scenarios are and what the API might look like. These conversations are happening under the banner of “open stylable shadow roots”. If you are interested in this, I highly recommend that you <a href="https://bkardell.com/blog/half-light.html">give Brian Kardell’s blog post a read</a> and check out <a href="https://github.com/bkardell/half-light">his half-light library</a>.</p><p>Brian is eagerly seeking feedback on scenarios and APIs to help shape a future HTML/CSS standard. Please give half-light a try in your own Web Components and help us out in shaping the future of the Web.</p><h3>Wrapping Up</h3><p>I hope this article helps dispel the myth that you can’t use global styles with Shadow DOM. A very small amount of JavaScript that interacts with document.styleSheets and adoptedStyleSheets will often do the trick. One simple helper function can enable scenarios in VanillaJS, FAST, Lit, and DSD. For more advanced, nuanced, and automatic techniques, a small 100 LOC library like <a href="https://github.com/bkardell/half-light/tree/main">half-light</a> can be used, a steppingstone to a potential new HTML/CSS standard.</p><p>If you enjoyed this look into Web Standards, please let me encourage you to consider purchasing <a href="https://www.bluespire.com/p/web-component-engineering">my Web Component Engineering course</a> for yourself or your team. I’d also love it if you would <a href="https://eisenbergeffect.medium.com/subscribe">subscribe to this blog</a>, <a href="https://www.youtube.com/@EisenbergEffect">subscribe to my YouTube channel</a>, or <a href="https://twitter.com/eisenbergeffect">follow me on twitter</a>. Your support greatly helps me continue writing and bringing this kind of content to the broader community. Thank you!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5b80e802e89d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Web Components 2024 Winter Update]]></title>
            <link>https://eisenbergeffect.medium.com/web-components-2024-winter-update-445f27e7613a?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/445f27e7613a</guid>
            <category><![CDATA[html]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[shadow-dom]]></category>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[css]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Wed, 17 Jan 2024 14:16:33 GMT</pubDate>
            <atom:updated>2024-01-19T17:04:01.949Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ObpSxTjcdKFQwzfKJ1cEOw.jpeg" /></figure><p>Last week, members of <a href="https://www.w3.org/community/webcomponents/">the W3C Web Components Community Group</a>, including browser vendors, library authors, and community members, met face-to-face to discuss new Web Component standards and platform improvements. In this post, I’ll go over the wide variety of topics that were covered.</p><h3>Day 1</h3><h4>Declarative Shadow DOM (DSD)</h4><p>Day 1 of the event started out with some great news. <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> is on track to ship DSD in version 123 or 124. Based on <a href="https://whattrainisitnow.com/calendar/">the Firefox release calendar</a>, that means we’ll see full cross-browser support for this paradigm-changing feature in February or March of this year.</p><blockquote><strong>UPDATE</strong>: Firefox has greenlit DSD for version 123, going out February 20!</blockquote><p>If you aren’t familiar with DSD, it’s a new standard that enables creating Shadow DOM in HTML without writing any JavaScript. Declarative Shadow DOM enables a bunch of interesting scenarios:</p><ul><li>Pseudo custom components with fully encapsulated styles and slot-based composition, without JavaScript.</li><li>Out of order, deferred content streaming with no need for JavaScript.</li><li>Improved declarative separation of HTML intended for semantic vs. presentation purposes.</li><li>A standard output for server-rendered Web Components.</li><li>An improved migration path from global CSS techniques to more performant, modular CSS.</li><li>An improved migration path to more modern, component-oriented HTML architectures.</li><li>New progressive enhancement approaches.</li></ul><p>If you are interested in learning more about Shadow DOM, including the new declarative capabilities, I’ve got an entire module in my Web Component Engineering course dedicated to the topic.</p><p><a href="https://bluespire.com/p/web-component-engineering">You can learn more here.</a></p><h4>CSS Slot Content Detection</h4><p>When building Web Components or using Shadow DOM, there’s often a desire to alter styles based on whether something is slotted into a particular location. For example, imagine a button component that needs to alter its internal spacing based on whether or not an image was slotted into the button. Today, this is accomplished by adding an event listener to the slotchange event and altering the styles based on the assignedNodes().</p><p>Because this scenario is so common, the Web Components CG has been working with the CSS WG and browser vendors to extend CSS with a new fully declarative mechanism that enables styling based on slotted state.</p><p>There are various approaches that could be taken to solve this problem in CSS, such as using a combinator or a pseudo-selector. While a combinator would be more powerful, it may also cause significant complexity and even performance problems. In light of this, the group seemed to favor the simpler approach of pursuing a pseudo-selector that is very targeted in scope, which could be shipped faster and with less risk.</p><p>The group is following up in W3C on next steps to get an official spec in place following these discussions. If you want to learn more about today’s techniques for this scenario, the Form Integration module of <a href="https://bluespire.com/p/web-component-engineering">my Web Component Engineering course</a> covers this in detail.</p><h4>Scoped Element Registries</h4><p>Today, all custom elements are defined globally in HTML. While this works fine for many sites and small to medium applications, it becomes a challenge in larger modular apps, mini-apps, micro-frontends, and other more complex scenarios. To address this need, the ability to create scoped registries of components and associate them with Shadow DOM has been proposed.</p><p>Fortunately, towards the end of last year, the scoped registry feature was successfully prototyped in Chromium. This enabled a few open questions and important details to be worked out. The CG agreed that the next steps are to update the spec based on lessons learned from the prototype and build out further prototypes in other browsers. We’re actively working on getting together the people to make this happen, with volunteers already stepping forward to implement cross browser.</p><p>If you are interested in learning more about custom element registries, as well as best practices for exporting, registering, and distributing Web Components, I’ve got quite a bit of content <a href="https://bluespire.com/p/web-component-engineering">available here</a>, where I walk through creating a Web Component-based design system.</p><h4>ARIA Mixin</h4><p>I’m very happy to report that with the release of Firefox 119 in October 2023, every major browser has now shipped support for ARIA Mixin string reflection via ElementInternals. This means that string-based ARIA attributes can be set through ElementInternals without needing to create attributes manually on the element’s host, enabling cleaner HTML and the ability for the component consumer to override default ARIA settings with their own.</p><blockquote><strong>ASIDE</strong>: For more information on using ARIA Mixin, as well as how to write your own polyfill for older browsers, <a href="https://bluespire.com/p/web-component-engineering">see my course</a>. 😆</blockquote><h4>Cross Shadow Root ARIA</h4><p>One of the things that’s not possible out-of-the-box with custom elements is connecting ID references across shadow roots. For example, if you are building a custom input control and you want a label outside of the element to be able to target an input inside of the element’s shadow DOM. Over the years, there have been a number of proposals for how to improve this, but this year I saw what I think is the most promising proposal so far, one that balances ease of use with power. The new proposal is called <a href="https://github.com/WICG/aom/pull/207">Reference Delegate for Cross-root ARIA</a>.</p><p>Using the proposed feature, a shadow root could specify an element that all ID refs should be delegated to. So, when a label’s for attribute references the host element, it will delegate through to the shadow root’s declared reference delegate. Here’s some code that shows the most basic scenario, using the imperative API:</p><pre>&lt;script&gt;<br>customElements.define(&quot;fancy-input&quot;, <br>  class FancyInput extends HTMLElement {<br>    constructor() {<br>      super();<br>      this.attachShadow({ mode: &quot;open&quot; });<br>      this.shadowRoot.innerHTML = `&lt;input id=&quot;real-input&quot; /&gt;`;<br>      this.shadowRoot.referenceDelegate = &quot;real-input&quot;; // magic!<br>    }<br>  }<br>);<br>&lt;/script&gt;<br><br>&lt;!--This label will target the internal real-input of fancy-input --&gt;<br>&lt;label for=&quot;fancy-input&quot;&gt;Fancy input&lt;/label&gt;<br>&lt;fancy-input id=&quot;fancy-input&quot;&gt;&lt;/fancy-input&gt;</pre><p>There’s also a no JS, declarative version of this for use with DSD:</p><pre>&lt;label for=&quot;fancy-input&quot;&gt;Fancy input&lt;/label&gt;<br>&lt;fancy-input id=&quot;fancy-input&quot;&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;<br>            shadowrootreferencedelegate=&quot;real-input&quot;&gt;<br>    &lt;input id=&quot;real-input&quot; /&gt;<br>  &lt;/template&gt;<br>&lt;/fancy-input&gt;</pre><p>One of the nicest details of the feature design is that it works with any attribute that refers to another element by ID string. Here’s the list as outlined in the RFC:</p><ul><li>aria-activedescendant</li><li>aria-controls</li><li>aria-describedby</li><li>aria-details</li><li>aria-errormessage</li><li>aria-flowto</li><li>aria-labelledby</li><li>aria-owns</li><li>for</li><li>form</li><li>list</li><li>popovertarget</li><li>invoketarget</li><li>interesttarget</li><li>headers</li><li>itemref</li></ul><p>The attendees seemed to be pretty pleased with this proposal, particularly since it makes the most common scenario very simple and works both imperatively and declaratively in an intuitive way. There was much more discussion around complex scenarios, which the RFC proposes to solve via the referenceDelegateMap. This allows mapping different ID refs to different elements. Here’s an example of using the declarative API for that:</p><pre>&lt;input role=&quot;combobox&quot;<br>       aria-controls=&quot;fancy-listbox&quot;<br>       aria-activedescendant=&quot;fancy-listbox&quot; /&gt;<br>&lt;fancy-listbox id=&quot;fancy-listbox&quot;&gt;<br>  &lt;template shadowrootmode=&quot;open&quot;<br>            shadowrootreferencedelegatemap=&quot;aria-controls: real-listbox,<br>                                            aria-activedescendant: option-1&quot;&gt;<br>    &lt;div id=&quot;real-listbox&quot; role=&quot;listbox&quot;&gt;<br>      &lt;div id=&quot;option-1&quot; role=&quot;option&quot;&gt;Option 1&lt;/div&gt;<br>      &lt;div id=&quot;option-2&quot; role=&quot;option&quot;&gt;Option 2&lt;/div&gt;<br>    &lt;/div&gt;<br>  &lt;/template&gt;<br>&lt;/fancy-listbox&gt;</pre><blockquote><strong>NOTE:</strong> The map can be updated dynamically. So, the mapping from aria-activedescendant to option-1 can be changed based on user interaction.</blockquote><p>While this is a bit more complicated than the simple referenceDelegate it still seemed to me to be a pretty intuitive way to handle the situation. I’ll be keeping an eye on this proposal, hoping it comes to browsers soon.</p><h3>Day 2</h3><h4>Open Stylable</h4><p>The term “open stylable” loosely refers to a collection of use cases related to enabling global CSS to take effect inside of the Shadow DOM of specific Web Components. This is most often requested by folks who want to use Web Components, but are required to integrate with a global stylesheet, which they often have no control over. But that is not the only scenario.</p><p>Broadly speaking, there are ways to handle this today, using a very small amount of JS in combination with Adopted StyleSheets. But there isn’t a standardized or fully declarative mechanism that works without JS.</p><p>There was a lot of discussion within the group on this topic. The biggest challenge seemed to be lack of clarity surrounding use cases. Over the years, this issue has been raised several times, but often without clear real-world use cases, or with wildly different needs. There have often been differences in scenarios, which then lead to subtlety different proposals for how to address the issue, which prevented any sort of consensus in the CG or community…and of course that resulted in no standard being established.</p><p>To try to move this in a more productive direction, the CG has asked for the submission of classic user stories, detached from any specific technical solution. In other words, rather than making a specific feature request, the group needs to understand what folks are trying to accomplish. It may be that the solution actually involves adding more than one feature in the end, but it’s hard to see that without the real stories to drive the overall platform design.</p><h4>Container Style Queries</h4><p>CSS Container <em>Size </em>Queries have been implemented in all the major browsers today. But only Chromium has shipped CSS Container <em>Style </em>Queries for custom properties. During the CG, time was taken to make sure that each of the browsers was on board with custom property-based container style queries. There was strong consensus. So, with the spec written, one shipped implementation, and cross browser consensus, we’re now just waiting for the additional Firefox and WebKit implementations to be completed and shipped.</p><blockquote><strong>ASIDE</strong>: If you want to learn more about CSS Container Size and Style queries, particularly in the context of Web Components, <a href="https://bluespire.com/p/web-component-engineering">check out my Web Component Engineering course</a>. Dang, there’s a lot of Web Standards goodness packed into that course…</blockquote><h4>CSS Module Scripts, Imports, and the @sheet Proposal</h4><p>The basic CSS Module Scripts feature has been standardized and is moving forward. If you aren’t familiar with that, here’s an example from <a href="https://bluespire.com/p/web-component-engineering">my Web Component engineering course</a>:</p><pre>import styles from &quot;./ui-card.css&quot; with { type: &quot;css&quot; };<br><br>const template = `<br>  &lt;slot&gt;&lt;/slot&gt;<br>`;<br><br>export class UICard extends HTMLElement {<br>  constructor() {<br>    super();<br>    this.attachShadow({ mode: &quot;open&quot; }).innerHTML = template;<br>    this.shadowRoot.adoptedStyleSheets.push(styles);<br>  }<br>}<br><br>customElements.define(&quot;ui-card&quot;, UICard);</pre><p>Notice how a CSS file can be directly imported using the import attribute “css” type. When that happens, the default export is a CSSStyleSheet instance which can be passed directly to a shadow root’s adoptedStyleSheets array.</p><p>The Web Components CG is working with the W3C CSS WG to expand CSS imports<a href="https://github.com/w3c/csswg-drafts/issues/5629"> to enable specifying multiple sheets inside of a single file</a>. This would allow bundling component CSS into a single CSS file while keeping each sheet isolated to the Shadow DOM that it is imported into. To enable this, a @sheet syntax is being proposed. Here’s what that would look like in CSS and how it could be used in a Web Component.</p><pre>/* components.css */<br><br>@sheet uiCard {<br>  :host {<br>    box-shadow: 0 0 .5rem rgba(0,0,0,0.15);<br>    border: .075rem solid #d8d8d8;<br>    border-radius: .375rem;<br>  }<br>}<br><br>@sheet uiButton {<br>  :host {<br>    ...<br>  }<br>}</pre><pre>import { uiCard as styles } from &quot;./components.css&quot; with { type: &quot;css&quot; };<br><br>const template = `<br>  &lt;slot&gt;&lt;/slot&gt;<br>`;<br><br>export class UICard extends HTMLElement {<br>  constructor() {<br>    super();<br>    this.attachShadow({ mode: &quot;open&quot; }).innerHTML = template;<br>    this.shadowRoot.adoptedStyleSheets.push(styles);<br>  }<br>}<br><br>customElements.define(&quot;ui-card&quot;, UICard);</pre><p>Once this feature is available, Web Component libraries that use this technique to distribute their CSS will not only gain the benefit of improved performance but will also make it much easier for component consumers to customize their styles. Consumers would simply provide their own CSS file to replace the originally distributed file.</p><blockquote><strong>NOTE</strong>: There is a separate proposal for declaratively importing CSS into a DSD. My hope is that however that feature works out in the end, it will also integrate with the new @sheet capability. With that in place, the entire example above could be done without any JavaScript.</blockquote><h4>HTML Modules and Declarative Custom Elements</h4><p>With <a href="https://github.com/tc39/proposal-import-attributes">JS Import Attributes</a> enabling the import of JSON and CSS, the group wanted to re-open earlier discussions about the possibility of HTML modules. Ultimately, there was consensus that something like this was needed, but that this might not be the right time yet.</p><p>In fact, as discussion proceeded, the group wondered whether it might be better to first explore fully declarative custom elements. There has been huge, long-time interest in this. Perhaps a minimal declarative format could be developed more quickly, and then expanded over time. The group resolved to explore this further, with a note that if a fully declarative format could be standardized, browsers could make further optimizations, particularly around caching binary representations of components and reusing them across page loads. Firefox has some previous experience doing just this with XBL and was very interested.</p><h3>Wrapping Up</h3><p>All-in-all, it was a great winter meetup. It was particularly nice to see features like ARIA Mixin shipped cross browser and get confirmation from Firefox that DSD was coming very soon. Discussions around in-progress proposals and standards were productive with a solid set of action items to help move forward. It looks like 2024 is going to be another great year for Web Components!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=445f27e7613a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Thank You Sale!]]></title>
            <link>https://eisenbergeffect.medium.com/thank-you-sale-fa2b8c46d631?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/fa2b8c46d631</guid>
            <category><![CDATA[web-standards]]></category>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[training]]></category>
            <category><![CDATA[html]]></category>
            <category><![CDATA[ui-engineering]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Tue, 21 Nov 2023 14:16:52 GMT</pubDate>
            <atom:updated>2023-11-21T14:16:52.571Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dpmxcpEfsdqARqSHNYHcaQ.jpeg" /></figure><p>A huge <strong>THANK YOU</strong> goes out to everyone who purchased <a href="https://bluespire.com/p/web-component-engineering">the Web Component Engineering course</a> over the last month! It was an ambitious project for me to put together, but you made it all worthwhile!</p><p>Feedback on <a href="https://bluespire.com/p/web-component-engineering">the course</a> has been great so far and in the time since launch, students have collectively completed over 1,500 lessons. Awesome! Go Web Components and Web Standards!</p><h3>Updates</h3><p>I am continuing to improve the content, its format, and the experience with lots of new capabilities and additional learning opportunities coming over the next few months. The goal is that the content not only serves as a set of initial lessons to learn and practice, but also as the best on-going and up-to-date Web Component standards reference available.</p><h3>Sale</h3><p>With the hope that many more people can benefit from this resource, I’ve put together a new sale during Thanksgiving. If you weren’t able to take advantage of the pre-sale, I’m happy to offer a Thanksgiving discount, from today through the end of Monday, November 27th.</p><p><strong>For the next week only, get 10% off</strong> <a href="https://bluespire.com/p/web-component-engineering">the Web Component Engineering course</a> by using code <strong>GRATITUDE </strong>at checkout.</p><p>Happy Thanksgiving!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fa2b8c46d631" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Web Component Engineering Course is Live!]]></title>
            <link>https://eisenbergeffect.medium.com/the-web-component-engineering-course-is-live-d5eaa46e61d0?source=rss-257e6cfa66b3------2</link>
            <guid isPermaLink="false">https://medium.com/p/d5eaa46e61d0</guid>
            <category><![CDATA[course]]></category>
            <category><![CDATA[learning-to-code]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[web-standards]]></category>
            <dc:creator><![CDATA[EisenbergEffect]]></dc:creator>
            <pubDate>Tue, 07 Nov 2023 14:16:59 GMT</pubDate>
            <atom:updated>2023-11-07T14:16:59.055Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qSXx3tVM2GSNjiNbEFxMIA.jpeg" /></figure><p>Today, I’m excited to announce that after months of work, <a href="https://bluespire.com/p/web-component-engineering">my Web Component Engineering course</a> is now live! 🎊</p><p>Back in April, I first sat down to figure out what I wanted to do with my business, and how I could advance Web Standards and help lift up engineers who desire to grow in modern UI Engineering with these standards. When I looked across the industry, there seemed to be a profound absence of solid training and quality content focused on Web Standards. That’s when I ramped up writing content on this blog and decided to create the Web Component Engineering course. My hope is that these two resources (and more I’ve got planned for next year) will begin to fill in the gaps while sharing some of the unique things I’ve had the opportunity to learn over the years.</p><p>It has taken months of writing documentation, creating code examples, building a design system, building a couple of applications, filming videos, editing, and more, but the course is now finally live! I learned so much through this process, and I’m excited to finally get this content into your hands.</p><p>Please let me extend a special thank you to all the early birds who purchased the course during the pre-sale period. It has been very encouraging to me to see the wonderful response, which greatly helped energize me in getting everything over the finish line.</p><p>Special thanks also go out to <a href="https://twitter.com/claviska">Cory LaViska</a> of <a href="https://shoelace.style/">Shoelace</a>, who gave me some great business advice at a critical juncture, as well as my old friend and one of the most talented UI/UX designers I know, <a href="https://twitter.com/scriswell">Scott Criswell</a>, who brought a unique designer’s perspective and was a constant source of feedback throughout my creative process. Finally, thank you to all my reviewers and testers over the last month or two.</p><h3>Discounts</h3><p>I’ve decided to extend the pre-sale discount through the end of today. So, if you are just finding out about this now, you can still use the code <strong>EARLYBIRD </strong><a href="https://bluespire.com/p/web-component-engineering">to purchase the course</a> and save $100. This is the last time that a discount of this size will be offered on this course, so don’t miss out.</p><h3>The Learning App</h3><p>One of the unique things about this course is the custom learning app that I built for the content. It has evolved quite a bit over the last couple of months and will continue to gain new features in the future, so that the content can grow in value even after students complete the course.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NtP-3bv8er1IZ1LSpEPbJg.png" /></figure><p>The fun thing about this app is that it’s all built with Web Components, using no libraries or frameworks. It’s built with the same code that we develop in the course itself and uses a number of the same components that we build. I really wanted to dog-food the course material as much as possible and provide the best experience for students. This seemed like a fun way to do that.</p><h3>Group Rates</h3><p>As a reminder, if you are an engineering leader looking for content to help train your team, or if you are an individual engineer who thinks that you can find several other people at your company interested in this content, I offer a variety of group licensing options. So, please reach out to sales at bluespire dot com for more information on that.</p><h3>Purchasing Power Parity (PPP)</h3><p>Now that we are launched, we will be able to begin offering PPP pricing for select countries starting next week. Please see <a href="https://www.bluespire.com/p/ppp">our PPP page</a> for more information and instructions on how to pursue this option.</p><h3>Wrapping Up</h3><p>It’s been a wild ride for me over the last several months, but the release day is finally here. I sincerely hope that everyone enjoys this course and that it’s the first of many opportunities I can share with you.</p><p>Happy learning!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d5eaa46e61d0" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>