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

svelte-algolia

v0.3.2

Published

Algolia server-side index updater and client-side search component for Svelte projects

Downloads

39

Readme

Tests pre-commit.ci status NPM version Netlify Status Open in StackBlitz

Live Demo

Utility for server-side Algolia index updates plus a client-side search component for Svelte apps. Only adds a single dependency:

There are 3 steps to setting up svelte-algolia:

  1. npm install --dev svelte-algolia
  2. Setup your server-side index updates.
  3. Integrate the client-side search component into your site.

2. Server-Side Index Updates

  1. Create an algoliaConfig object:

    import 'dotenv/config' // optional
    
    async function loadPokedex() {
      const json = await import('pokedex.json')
      return json.default.map((el) => ({ ...el, id: el.someUniqAttribute }))
    }
    
    const algoliaConfig = {
      appId: process.env.VITE_ALGOLIA_APP_ID,
      // don't prefix admin key with VITE_ else it would get exposed to client-side code
      apiKey: process.env.ALGOLIA_ADMIN_KEY,
      indices: [
        { name: `Pokedex`, getData: loadPokedex },
        { name: `Hitchhiker's Guide`, getData: guideLoader },
      ],
      settings: {
        attributesToHighlight: [`name`],
      },
    }

    The getData function is expected to return an array of objects containing the data you wish to index (a product catalog, blog posts, documentation pages, pokémons or whatever). Each object in the data array should have a key named id, _id or objectID for Algolia to recognize it and overwrite existing data where appropriate.

    The settings object applies to all indices. You can also pass a settings object to each index individually which overrides the general one.

  2. Pass your config to indexAlgolia:

    import { indexAlgolia } from 'svelte-algolia/server-side'
    
    indexAlgolia(algoliaConfig)

    You can call this function wherever you'd like to update your indices, e.g. in svelte.config.js or in src/hooks.ts (as this demo site does). Typically, you would include this in every production build of your app.

Config Options

const defaultConfig = {
  verbosity: 1, // 0, 1 or 2 for no/some/lots of logging
  partialUpdates: false, // if true, figures out diffs between existing
  // items and new ones and only uploads changes, otherwise, completely
  // overwrites each index on every call to indexAlgolia()
  matchFields: [], // (only used when partialUpdates is true) keys of fields to check
  // for whether an item has changed; could e.g. be a timestamp, hash or an ID that's
  // updated every time the item has changed; if not provided, items are checked for
  // deep-equality to discover changes which can become slow for thousands of items
  settings: {}, // an object of Algolia index settings that applies to all indices
  // see https://algolia.com/doc/api-reference/settings-api-parameters for available options
  // can be overridden for individual indices by passing a settings object as part of the indices array:
  // indices = [{ name: `pokedex`, ..., settings: { foo: `bar` }}],
}

Auto-update Indices during Builds

To use this package as part of a build process (e.g. in a SvelteKit app), simply call indexAlgolia in your build config:

// svelte.config.js

// only update Algolia indices on production builds (saves API quota)
if (process.env.NODE_ENV === `production`) {
  const { indexAlgolia } = await import(`svelte-algolia/server-side`)

  const algoliaConfig = {
    // see above
  }
  indexAlgolia(algoliaConfig)
}

3. Client Side UI

<Search /> needs your Algolia app's ID and search key to access its search indices as well as a mapping from index names to corresponding Svelte-component that should render search hits coming from that index. Each hit component receives a hit object as prop with all attributes stored in the Algolia index.

<script>
  import Search from 'svelte-algolia'
  import PokemonHit from '../components/PokemonHit.svelte'

  const appId = '0OJ5UL9OJX'
  const searchKey = '63f563566cdd6de606e2bb0fdc291994'
  // in a real app you'd get your credentials like this:
  const appId = import.meta.env.VITE_ALGOLIA_APP_ID
  const searchKey = import.meta.env.VITE_ALGOLIA_SEARCH_KEY
</script>

<header>
  <nav>{...}</nav>
  <Search
    {appId}
    {searchKey}
    indices={{ Pokedex: PokemonHit }}
    placeholder="Search Pokedex" />
</header>

For example, the PokemonHit.svelte component on the demo site looks like this:

<script>
  export let hit
</script>

<h2>{@html hit.name}</h2>

<div>
  <ul>
    <li>Type: {@html hit.type.join(`, `)}</li>
    <li>Height: {@html hit.height}</li>
    <li>Weight: {@html hit.weight}</li>
    <li>Weaknesses: {@html hit.weaknesses.join(`, `)}</li>
  </ul>
  <img src={hit.img} alt={hit.nameOrig} />
</div>

<style>
  /* highlights text matching the search string */
  :global(em) {
    color: darkcyan;
    line-height: 1.2em;
    border-radius: 3pt;
    font-style: normal;
  }
  div {
    display: flex;
    justify-content: space-between;
  }
</style>

Substrings in attributes matching the current search string will be wrapped in <em> which need the {@html ...} tag to be rendered correctly but can then be styled to highlight why a particular hit matches the current search string. The original value (i.e. without <em> tags) of every highlighted attribute is available as hit.[attr]Orig. See hit.nameOrig above.

Props

Full list of props/bindable variables for this component:

  1. appId: string

    Algolia app ID

  2. ariaLabel: string = `Search`

    Tells assistive technology how to announce the input element to the user.

  3. hasFocus: boolean = false

    Bindable boolean indicating whether the text input or results pane currently has focus.

  4. indices: Record<string, typeof SvelteComponent> | [string, typeof SvelteComponent][]

    Object mapping the name of each index the Search component should tap into for finding Search results to the Svelte component that should render those hits.

  5. input: HTMLInputElement | null = null

    Handle to the DOM node.

  6. loadingMsg: string = `Searching...`

    String to display in the results pane while Search results are being fetched.

  7. noResultMsg = (query: string): string => `No results for '${query}'`

    Function that returns the string to display when search returned no results.

  8. placeholder: string = `Search`

    Placeholder shown in the text input before user starts typing.

  9. query: string = ``

    Current value of the DOM node.

  10. resultCounter = (hits: SearchHit[]): string =>
      hits.length > 0 ? `<span>Results: ${hits.length}<span>` : ``

    Function that returns a string which will be displayed next to the name of each index to show how many results were found in that index. Return empty string to show nothing.

  11. searchKey: string

    Search-only API key

Events

Search.svelte listens for on:close events on every hit component it renders and will set hasFocus to false to close itself when received. You can use this e.g. to close the search interface when the user clicks on a link in one of the search results and navigates to a different page on your site:

<script>
  import { createEventDispatcher } from 'svelte'

  export let hit

  const dispatch = createEventDispatcher()
</script>

<h3>
  <a href={hit.slug} on:click={() => dispatch(`close`)}>{@html hit.title}</a>
</h3>
<p>{@html hit.body}</p>

It also emits a focus event every the user clicks the search icon and focus enters the text input.

<Search on:focus={() => console.log("Let's search!")} />

Styling

Search.svelte offers the following CSS variables listed here with their defaults (if any) that can be passed in directly as props:

  • button
    • color: var(--search-icon-color)
  • h2
    • color: var(--search-heading-color)
  • input
    • background: var(--search-input-bg)
    • color: var(--search-input-color)
    • font-size: var(--search-input-font-size, 1em)
  • input::placeholder
    • color: var(--search-input-color)
  • input.hasFocus + button
    • color: var(--search-input-color)
  • div.results
    • background-color: var(--search-hits-bg-color, white)
    • box-shadow: var(--search-hits-shadow, 0 0 2pt black)

For example:

<Search
  indices={{ Pages: SearchHit, Posts: SearchHit }}
  {appId}
  {searchKey}
  --hitsBgColor="var(--search-body-bg)"
  --inputColor="var(--search-text-color)"
  --iconColor="var(--search-link-color)"
/>

The top level element is an aside with class svelte-algolia. So you can also style the entire DOM tree below it by defining global styles like

:global(aside.svelte-algolia input button svg) {
  /* this would target the search icon */
}
:global(aside.svelte-algolia div.results section h2) {
  /* this would target the heading shown above the list of results for each index */
}

Examples

Some sites using svelte-algolia in production:

Using svelte-algolia yourself? Submit a PR to add your site here!

Want to contribute?

PRs are welcome but best open an issue first to discuss changes.

The app ID and search key .env were intentionally committed so you can clone this repo and work on it without having to create your own index first. To try out your changes in a dev server running locally, use

git clone https://github.com/janosh/svelte-algolia
cd svelte-algolia
sed -i.bak 's/name: `Pokedex`/name: `Pokedex Clone`/' svelte.config.js
npm install
npm run dev

Note the sed command that changes the index name in site/svelte.config.js from 'Pokedex' to 'Pokedex Clone' so you don't accidentally mess up the search index for this demo site while developing.