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

sylvite

v1.0.5

Published

![CI](https://github.com/cha0s/sylvite/actions/workflows/ci.yml/badge.svg)

Downloads

394

Readme

CI

Sylvite 🧩

✨ Sylvite is a radically-extensible system that lets you register and implement hooks to extend your Vite app to the stars and beyond ✨.

Getting started 🪄

npm install sylvite

Sylvite hooks into your Vite build process. Initial setup involves hooking into your Vite plugins:

import sylvite from 'sylvite';
import {defineConfig} from 'vite';

const {hooks} = await sylvite({
  manifest: {},
  meta: import.meta,
});

export default defineConfig({
  // ...
  plugins: hooks.call('vitePlugins', [
    // any other Vite plugins
  ]),
  // ...
});

By default, not much happens. The magic is what has just been enabled by the Sylvite plugin.

Manifest and entries 🛠️

In the example above, we simply passed {} as the manifest. By default, this will only add Sylvite core. This isn't exactly exciting. In order to do novel things, we need to add more to our manifest.

Let's contrive an example. Say we have a file structure like this:

src
└─ coolio
   ├─ build.js
   └─ client.js

We could add src/coolio to our manifest:

const {hooks} = await sylvite({
  manifest: {
    './src/coolio': {},
  },
  meta: import.meta,
});

This will pull in any entry modules under this path.

The default (and at this point, only) entry is 'build'. This means src/coolio/build will be imported and its hook registrations and implementations executed.

So far we can add modules to the manifest that implement a 'build' entry, but we're still in node land here in vite.config.js. Why did we even add a Vite plugin? I'm glad you asked!

Runtime (virtual modules) 🤩

Once Vite starts compiling, we are in runtime. In runtime, arbitrary entries may be created. That means you can create as many as you want and call them pretty much whatever you want.

For example, let's say we wanted to have a 'client' entry that registers, implements, and invokes hooks in our client-side JS. To do this, all we have to do is import a virtual module following a simple naming convention:

import {hooks} from 'virtual:sylvite/client';

hooks.call('coolio:forExampleClientInitialize', window || 'whatever');

Your virtual module exports the following named exports:

{
  hooks,  // all registered hooks
  loaded, // the manifest modules for this entry
}

The virtual module naming convention is simply virtual:sylvite/${YOUR_ENTRY}. Upon import, a virtual module is created if it does not already exist.

The virtual module will automatically discover all entries that exist in any modules in your manifest. So in our example above, this import will now import src/coolio/client and handle all of its hooks. Sweet!

Hooks 🪝

The hook system is implemented using tapable.

Registration

Hooks are registered by exporting a register function from your entry:

export function register({tapable: {SyncHook}}) {
  return {
    forExampleClientInitialize: new SyncHook(['window']),
  };
}

The following context is passed to your register function:

{
  config,   // the manifest configuration for this entry
  loaded,   // the manifest modules for this entry
  manifest, // the entire manifest
  meta,     // the meta passed in, if any
  path,     // the manifest path (the key in the manifest file)
  tapable,  // tapable, so you don't have to import it yourself
}

Implementation

Hooks are implemented by exporting an implement function from your entry:

export function implement({hooks}) {
  hooks.tap('coolio:forExampleClientInitialize', (window) => {
    window.alert('hello world!');
  });
}

The following context is passed to your implement function:

{
  config,   // the manifest configuration for this entry
  hooks,    // all registered hooks
  loaded,   // the manifest modules for this entry
  manifest, // the entire manifest
  meta,     // the meta passed in, if any
  path,     // the manifest path (the key in the manifest file)
  tapable,  // tapable, so you don't have to import it yourself
}

Ordering

Hooks can be ordered before or other implementations by using .before() and .after():

export function implement({hooks}) {
  hooks.before('sylvite').tap('coolio:forExampleClientInitialize', (window) => {
    window.alert('hello world!');
  });
}

That hook will run before any implemented by 'sylvite'.

Stubbing

By default, Sylvite will silently ignore e.g.:

export function implement({hooks}) {
  hooks.tap('thisHookDoesNotExist', (window) => {
    // ...
  });
}

This may be configured in your manifest if you desire more stringent reporting:

const {hooks} = await sylvite({
  manifest: {
    'sylvite': {
      missingHookStrategy: 'ignore' | 'warn' | 'error',
    },
  },
  meta: import.meta,
});

The default is 'ignore' if unspecified. 'warn' will console.warn an error where the missing hook implementation was referenced. 'error' will throw the error instead.

Restart 🎶

When any 'build' entry file changes, the Vite dev server will restart automatically.

Q/A 💬

Why no TypeScript support?

This library is super duper dangerous. Stay safe; don't use it!

Why do I have to pass in import.meta?

import.meta is used to resolve entries and relative paths. You could theoretically pass in any object that implemented dirname and resolve, though I don't know why you'd want to.

How absolutely dare you!/Who would do such a thing?

😂

I'm experimenting with configuring e.g. entire remix route trees using this. It should be possible to package up an entire self-contained feature such as user accounts as a module that can simply be added to your app's manifest to fully implement the feature in your app.

I'm also experimenting with using this as a game modding platform.

Also, I just love taking extensibility to its absurd conclusion. We are using a dynamic programming language, remember?