Oops, Svelte did it again

Issue #224.September 21, 2023.2 Minute read.
Bytes

Today’s issue: Standing up to rogue polyfills, inventing the candy of the future with Ryan Dahl, and being put on trial for witchcraft in the developer town square.

Welcome to #224.


Eyeballs logo

The Main Thing

Rich Harris as Britney Spears

I'm not that innocent

Oops, Svelte did it again

Editorial Note: We’ve been busy shipping shirts from our launch last week, so we asked our good friend David East for some help with this story. He’s the Lead DevRel for idx.dev at Google and (as you can see) is a long time Britney Spears stan.

Back in 2019, Britney might have looked at Svelte and said, “You’re not that innocent.” Fast forward to now, Svelte’s compiler has jazzed up JavaScript into a reactive superstar, preaching the “Sometimes” less is the new more. And just as Britney’s style never stagnates, neither does tech. Enter Svelte 5 with its snazzy run-time runes, tipping a hat to old-school Knockout.js. And let’s be clear: it’s not just a glow-up, it’s “Stronger” than yesterday.

Svelte is known for its simple to understand reactivity system, because it’s just JavaScript.

<!-- Svelte 4 -->
<script>
let toxicity = 0;
let message;

$: {
  if (toxicity < 25) message = "Baby, one more time?";
  else if (toxicity < 50) message = "Oops... I did it again.";
  else message = "It's Toxic!";
}
</script>

<input type="range" min="0" max="100" bind:value={toxicity} />
<p>{message}</p>

Here, toxicity and message are reactive out-of-the-box because Svelte does total magic with its compiler to keep track of everything. Now, things can get a little odd with the $ syntax. This is a reactive statement and everything inside that block will run whenever a referenced value changes, such as toxicity.

This is a small amount of code for blissful reactivity because Svelte is doing the lifting for us. However, things start to break down in Svelte land once you leave the comfort of the compiler aided .svelte files and into the wild world regular JavaScript. But all of this (optionally) changes with Svelte 5.

Svelte 5 inches away from compile-time reactivity and pushes it into the run-time with a new (and optional) reactivity system called runes. These runes are $ dollar prefixed functions that work as reactive utilities. No, they are not RxJS’ Finnish notation, no they are not Reactive Transform in Vue, they are not Knockout.js observables, and they are certainly not signals in Solid.js. Actually, fact check. They indeed are signals under the hood. But in all seriousness, let’s break them down.

$state() - Svelte’s famous terse reactivity system gets a tiny bit more verbose. Instead of the automatic assigned reactivity, you now declare what is reactive with a $ref() $state() rune. This might be an extra step (you mean, I have to actually think about what code updates now!?) but it comes with a ton of benefits with other runes.

<!-- Svelte 5 -->
<script>
  let toxicity = $state(0);
</script>

<input type="range" min="0" max="100" bind:value={toxicity} />

$derived() - This rune automatically recalculates derived values based on its dependencies. For bonus points, it can work inside of functions and receiving a function itself, a massive improvement from the $ reactive statements.

<!-- Svelte 5 -->
<script>
  let toxicity = $state(0);
  let message = $derived(level());

function level() {
  if (toxicity < 25) return "Baby, one more time?";
  else if (toxicity < 50) return "Oops!...I did it again.";
  else return "It's getting dangerously Toxic!";	
}
</script>

<input type="range" min="0" max="100" bind:value={toxicity} />
<p>{message}</p>

$effect() - Got some side-effects you want to run? Every time you make a change, Svelte’s got you covered. In addition, the Svelte creator, Rich Harris, claims this rune can replace lifecycle methods such as onMount().

<!-- Svelte 5 -->
<script>
let toxicity = $state(0);
let message = $state("");

$effect(() => {
  if (toxicity < 25) message = "Baby, one more time?";
  else if (toxicity < 50) message = "Oops!...I did it again.";
  else message = "It's getting dangerously Toxic!";		
});

</script>

<input type="range" min="0" max="100" bind:value={toxicity} />
<p>{message}</p>

$props() - Retrieve properties of a component which will remove the need to ever use export let to define component properties or use complex concepts like $$restProps.

<!-- Svelte 5 -->
<script>
  let { toxicity, message } = $props(); // no more `export let`
</script>

Bottom Line: It’s like Svelte took a page from Knockout’s 2010 tour guide. With Svelte 5’s reactivity powered by signals, we’re reminiscing about the days when Knockout was making waves with its reactive patterns. But while Svelte might be inspired by the classics, it’s serving its own modern twist. It’s like flipping open a Razr phone in 2023, but this time you can actually read hot takes on Twitter X while sipping on oat milk lattes and debating the merits of a four-day workweek.

        

Convex logo

Our Friends
(With Benefits)

Mr Burns from the Simpsons looking like a golden god

Me when I build a production-ready backend with TypeScript.

Convex is like Firebase on steroids

And by that, I mean it comes with more powerful features and is easier to use than Firebase — not that it will give you huge biceps and weird acne 🙏.

With Convex, you get a full cloud backend that can replace your database, server functions, and glue code. And it’s all 100% Type-safe.

Here’s what it comes with:

  • A realtime relational DB that’s reactive by default and simple to work with.

  • Easy-to-use server functions that manage dev and prod for you.

  • First-class React support with their React and Next.js client libraries (see the docs).

  • Helpful built-in features like file storage, search, and auth.

It’s perfect for using familiar frontend tools to quickly spin up a fullstack app with a production-ready backend.

Check out the free tier — and feel the true power of TypeScript.


Pop Quiz logo

Pop Quiz

Sponsored by Snyk

Want to choose the best runtime for your next project? Check out Snyk’s comparatative analysis on Node.js vs. Deno vs. Bun to see how they each stack up.

We have an existing filterRecommendations function that takes two arguments:

  1. purchasedProducts: A list of product IDs that a user has already purchased.
  2. potentialRecommendations: A list of product IDs that could be recommended to the user, which might include some products they have already purchased.

The goal is to generate a list of unique product recommendations, ensuring that products the user has already purchased are not included.

Given that information, how can this code be improved?

function filterRecommendations(purchasedProducts, potentialRecommendations) {
  return potentialRecommendations.filter(
    (product) => !purchasedProducts.includes(product)
  );
}

// Testing the function
console.time("Recommendations");
const purchasedProducts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const potentialRecommendations = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
const result = filterRecommendations(
  purchasedProducts,
  potentialRecommendations
);
console.log(result); // [11, 12, 13, 14, 15]
console.timeEnd("Recommendations"); // 0.34814453125 ms

Cool Bits logo

Cool Bits

  1. Mojtaba Seyedi wrote this Practical guide to the View Transitions API and meta frameworks. It turns out that using more View Transitions on your site might not actually mean you’re a better developer, after all.

  2. Next.js 13.5 comes with perf improvements for faster local server startup, faster HMR, and faster package imports. Next.js Conf is also coming up next month, so you might want to start planning which size medium black turtleneck you’re going to wear in solidarity with the Next team.

  3. Zero created an easy way to integrate 3rd-party API credentials into your project. Their SDK is available for TypeScript, Rust, Python, and Go. [sponsored]

  4. Safari 17.0 comes with WebKit support for the popover attribute, the JavaScript Storage API, Offscreen Canvas, and more.

  5. Evan Bacon wrote this RFC that proposes putting API Routes into Expo Router, which would bring server functions into the React Native ecosystem for the first time. That’s cool Evan, but wake me up when Expo supports all of the mystical runes of the ancients.

  6. Artem Zakharchenko wrote an article called One Thing Nobody Explained To You About TypeScript. And he’s not talking about the fact that if you publicly say that you don’t like using TypeScript, you will be tried as a witch in the developer town square.

  7. Product for Engineers is PostHog’s newsletter dedicated to helping engineers improve their product skills. Subscribe for free to get curated advice on building great products, lessons (and mistakes) from building PostHog, deep dives on top startups, and very cute hedgehog illustrations. [sponsored]

  8. Mantine 7.0 sees the popular React component library ditch Emotion and migrate to native CSS, plus a ton of other changes and updates. It’s a big one.

  9. Marvin Hagemeister just dropped another article in his series on Speeding up the JS Ecosystem. This one is called Polyfills gone rogue, and discusses how many popular npm packages depend on 6-8x more packages than they need to because of unnecessary polyfills.

  10. Deno v1.37 ships with built-in support for the Jupyter Notebook Kernel, which lets you create interactive REPL sessions and “unlock a whole new set of data science and machine learning possibilities.” Not gonna lie, when I first read the phrase Jupyter Kernels, I immediately pictured a futuristic candy that was half Pop Rocks and half Candy Corn. Sounds terrible. And incredible.


Pop Quiz logo

Pop Quiz: Answer

Sponsored by Snyk

function filterRecommendations(purchasedProducts, potentialRecommendations) {
  return potentialRecommendations.filter(
    (product) => !purchasedProducts.includes(product)
  );
}

We can improve this code by using a Set to store the purchasedProducts list. Because Set allows us to check for the existence of a value in constant time, we can avoid the linear time lookup that Array.includes requires. In theory, this should make the function significantly faster for large lists of products (but you should always benchmark your code to be sure).

function filterRecommendations(
  purchasedProducts,
  potentialRecommendations
) {
  const purchasedProductsSet = new Set(purchasedProducts);
  return potentialRecommendations.filter(
    (productId) => !purchasedProductsSet.has(productId)
  );
}

console.time("Recommendations");
const purchasedProducts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const potentialRecommendations = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
const result = filterRecommendations(
  purchasedProducts,
  potentialRecommendations
);
console.log(result); // [11, 12, 13, 14, 15]
console.timeEnd("Recommendations"); // 0.212890625 ms