npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

hydroactive

v0.1.6

Published

HydroActive is a experimental library for hydrating web components and adding "sprinkles of reactivity" to pre-rendered HTML.

Downloads

13

Readme

HydroActive

HydroActive is a experimental library for hydrating web components and adding "sprinkles of reactivity" to pre-rendered HTML.

Check out the YouTube video for an in-depth discussion and demo of HydroActive's features. Note that this video discusses a very early version of HydroActive. Versions up through 0.0.5 roughly align with that video, however HydroActive is currently being redesigned and rewritten, so it may not be up to date with the current version of the library.

Premise

Most server-side rendering / static site generation solutions bind the client and server together with a shared implementation. Components are "hybrid rendered" by supporting both the client and the server. They are rendered first on the server and usually props are serialized and passed to the client as JSON. The components then rerender on the client and take over interactivity.

While many frameworks tweak this approach in various ways, a few aspects of it have always bothered me:

  1. Why are the client and server coupled together? Why can't I use a different language or framework on my server or even change it without completely breaking my frontend application?
  2. Why are all components hybrid rendered? Most tend to be completely static and should be rendered exactly once on the server and never again on the client, but every component is implicitly rendered in both contexts.
  3. Why are we passing a JSON side-channel to the client? This seems unnecessary given that we already have the rendered HTML, why do we need to duplicate that information in a different structure?
  4. Why do I have to write my components to be hybrid-compatible? Why can't I write the server-side rendered components and directly read the file system, while my client-side rendered components could directly access browser APIs?
  5. Why do components need to rerender on the client when anything changes? 90% of a component is often static, when updating some text that's the only thing which should change. That's the only thing the client-side component should know how to change.

These are complaints of a generic strawman SSR/SSG solution, each framework is different and has different answers to these particular problems. Instead, I want to think of hydration through a different lens:

Mental model

Servers are good at rendering HTML and returning it in HTTP responses. That's kind of their thing, we don't need to reinvent that. If you have a Node, Java, Ruby, Haskell, C, or even Fortran server, any of them should be fine. How a server renders HTML is an unrelated implementation detail. HydroActive focuses on taking that pre-rendered HTML and making it interactive on the client.

This means we can think of hydration as a purely deserialization problem. Servers can render web components without any fancy tooling or integrations, all they need to do is render something like:

<my-counter>
    <div>The current count is <span>5</span>.</div>
    <button>Increment</button>
</my-counter>

Any server can do that, exactly how it does so is unimportant to HydroActive. From here, HydroActive makes it easy to load from this component and make it interactable. It does this by providing an API to define custom elements with useful lifecycle and convenient DOM APIs. One example would be:

import { ElementAccessor, defineComponent } from 'hydroactive';
import { WriteableSignal } from 'hydroactive/signals.js';

// `defineComponent()` creates a web component class based on the given hydrate
// function. The callback is invoked on hydration and provides `host` and `comp`
// parameters with additional functionality to provide interactivity to the
// pre-rendered component. Automatically calls `customElements.define` under the
// hood.
const MyCounter = defineComponent('my-counter', (host) => {
    // Interacting with a site using HydroActive is a three-step process:
    // 1. Query it - `host.query` queries the DOM for the selector and asserts
    // it is found.
    const countEl: ElementAccessor<HTMLSpanElement> = host.query('span#count')
        // 2. Hydrate it - `.access` asserts that the element does not need to
        // be hydrated. Alternatively, we could call `.hydrate()` to trigger
        // hydration on a sub-component.
        .access();

    // 3. Enhance it - `live` creates a `Signal` which wraps the `textContent`
    // of `countEl`. The text is interpreted as a `number` and implicitly
    // converted. Also whenever `count.set` is called, the `<span>` tag is
    // automatically updated.
    const count: WriteableSignal<number> = live(countEl, host, Number);

    // Prints `5`, hydrated from initial HTML content.
    console.log(count());

    // HydroActive providers ergonomic wrappers to read elements from the DOM,
    // assert they exist, and use them safely. It also types the result based on
    // the query, this implicitly has type `ElementAccessor<HTMLButtonElement>`.
    const incrementBtn = host.query('button').access();

    // HydroActive also provides ergonomic wrapper to bind event listeners.
    // This automatically removes and re-adds the listener when `<my-counter>`
    // is disconnected from and reconnected to the DOM.
    incrementBtn.listen(host, 'click', () => {
        // `count.set` automatically updates the underlying DOM with the new
        // value.
        count.set(count() + 1);
    });
});

// For TypeScript, don't forget to type `my-counter` tags as an instance of the
// class.
declare global {
    interface HTMLElementTagNameMap {
        'my-counter': InstanceType<typeof MyCounter>;
    }
}

See the demo for more cool features. You can run the demo locally with npm start. The HTML pages contain hard-coded, pre-rendered HTML (remember, how they get rendered by the server is an implementation detail). The TypeScript files house the component's implementations and demonstrate different forms of reactivity and use cases.

Internal

Testing

Run tests with npm test. Debug tests with npm run test-debug and then opening localhost:8000.

Publishing

  1. Make sure you're on the right Node version.
    nvm install
  2. Increment the version number in package.json.
  3. Make sure tests pass:
    npm test
  4. Update the version in the lockfile.
    npm install
    Make sure no other dependencies were updated.
  5. Then build and publish the package:
    npm run build
    (cd dist/ && npm publish)
    # Alternatively run `(cd dist/ && npm pack)` to inspect the tarball to be published.
  6. Commit, tag, and push the incremented version number.
    git add . && git commit -m "Release v0.0.1."
    git tag releases/0.0.1
    git push
    git push --tags
  7. Go to GitHub releases and create a new release with a changelog.