go back

Placeholder for post cover image
Cover image for post

An emoji dictionary in Svelte

May 24, 2019

As an avid user of the 🅱️ emoji, I get (too much) enjoyment out of the few alphabetical characters in the emoji alphabet.

But we can do more than just substitute a b with a 🅱️; I wanted to know many words can be written entirely with emoji. Let's find out!

First I found an (English) dictionary and wrote a quick & dirty Rust script to generate the words. Just a list of words isn't fun though, it needs interactivity! I chose Svelte for this to get some hands-on with its dev experience (it's pretty good!) and performance.

To start, I made a basic webpack config with svelte-loader and three files:

Note: if you want to skip to the end, the source is here.

JavaScript

In main.js, the words are imported and prepared for the component:

import words from 'output.txt';

// Associate letters & sequences with their 
// emoji equivalents
const emojiHash = {
    "id": "🆔",
    "a": "🅰️",
        ...
    "soon": "🔜"
};

// Replace the letters/sequences in a string as 
// their respective emoji
const convertToEmoji = (word) => {
    let emojified = String(word);
    regexKeys.forEach((key, i) => {
        emojified = emojified.replace(key, emojiHash[sortedKeys[i]];
    }));
    return emojified;
};

// Render letters/sequences as emoji by running 
// the above function until there are no letters
// remaining
function emojify(word) {
    let emojified = String(word);
    do {
        emojified = convertToEmoji(emojified);
    } while (emojified.split('').some(e => /^[a-zA-Z]+$/.test(e)));
    return emojified;
};

Enter fullscreen mode Exit fullscreen mode

Then the component is mounted to the DOM:

const app = new App({
    target: document.body,
    props: {
        emoji: Object.values(emojiHash),
        sort: 'default',
        words: words.split('\n').map(emojify)
    }
});
Enter fullscreen mode Exit fullscreen mode

Svelte

Great! Now we have formatted data coming into the component, let's do something with it.

*.svelte files are HTML files with some syntactic sugar. The basic structure is as follows:

<script>
  // Functions, variables
  export let words;

  function clicky(e) {
    console.log(e.target.innerText);
  }
</script>

<!-- Any styles associated with the component -->
<style>
  .container { 
    background: palevioletred; 
  }
</style>

<!-- The rendered markup -->
<div class="container">
  <ul>
    {#each words as word}
      <li>
        <p on:click={clicky}>
          {word}
        </p>
      </li>
    {/each}
  </ul>
</div>
Enter fullscreen mode Exit fullscreen mode

🎉 ta-da! 🎉 A list of words rendered with Svelte! Note that since words is being passed in as a prop, the export keyword is needed.

For the sake of brevity I'll just go through adding filtering (sorting is in the repo if you want to take a look).

Somewhere in the component, let's render a list of checkboxes for each emoji:

Filter:
{#each emoji as moji}
  <label>
    <input on:change={handleFilterClick} type="checkbox" checked={!filteredEmoji.includes(moji)} value={moji}>
    <span>{moji}</span>
  </label>
{/each}
Enter fullscreen mode Exit fullscreen mode

Since we're rendering the list via the words variable, we'll need to update it to reflect the filter.

<script>
  export let words;

  // Keep an immutable version of words in memory
  export let wordsImm = Array.from(words);

  function handleFilterClick(e) {
    const { checked, value } = e.target;

    // Keep immutable version of original prop & make a copy 
    // to apply filters to
    let wordsMut = Array.from(wordsImm);

    // Either add or remove the current emoji from the filters
    if (checked) {
      filteredEmoji.splice(filteredEmoji.indexOf(value), 1);
    } else {
      filteredEmoji.push(value);
    }

    // If there are filters, apply them to list of words
    if (filteredEmoji.length > 0) {
      filteredEmoji.forEach(emoji => {
        wordsMut = wordsMut.filter(word => !word.includes(emoji));
      });
    }

    // Set variable to new list
    words = wordsMut;
  }
</script>
Enter fullscreen mode Exit fullscreen mode

When words is updated to the filtered (mutated) version after selecting a filter, it will trigger an update and the DOM will render the new list.

Side-note: this could be refactored to have the filtering in the `{each}`, (preventing the need to update `words`) but I wanted to render the number of words in a different part of the component.

Final thoughts

Svelte is nice & fast! I plan to use it again, ideally for something more resource intensive/visually demanding to really push it to its limits (beyond where React would have issues).

I also want to see how it is to work on a larger project using Sapper, once the framework is more mature.

Go play with it here! https://bryce.io/emoji-dict

View source on Github.

go back