Getting Started

Tutorial: Load js-confetti

Learn how to load the js-confetti script using the Nuxt Scripts module.

Introduction

In this tutorial, you will learn how to load the js-confetti script using the Nuxt Scripts module.

You'll learn about the following:

  • What the useScriptNpm registry script is.
  • How to load the js-confetti script using it.
  • Adding types to loaded scripts.
  • Using proxied functions to call the script.

Background on useScriptNpm

To load the script, we'll be using the useScriptNpm.

This is a registry script, a supported third-party integration built on top of the useScript composable that allows you to load scripts from NPM.

When working with NPM files, you'd typically include them as a node_module dependency in the package.json file. However, optimizing the script loading of these scripts can be difficult, requiring a dynamic import of the module from a separate chunk and loading it only when needed. It also slows down your build as the module needs to be transpiled.

The useScriptNpm registry script abstracts this process, allowing you to load scripts that have been exported as immediately invokable functions, with a single line of code .

In many instances it will still make more sense to include the script as a dependency in the package.json file, but for scripts that are not used often or are not critical to the application, this can be a great alternative.

To begin with we can think of using this script as an alternative to the useHead composable. You can see an example of the abstraction layers in the following code sample.

useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
})

Loading the script

Within your one of your components, you'll want to load the script. You can do this by using the useScriptNpm registry script.

app.vue
<script setup lang="ts">
useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
})
</script>

If you check your browser requests, you should see the script being loaded.

Resolving the third-party script API

Now that the script is loaded, you can use it in your component. To do so we need to tell the underlying API how to use the script, for this we can leverage the use function.

This function is only called on the client-side and is used to resolve the third-party script.

app.vue
<script setup lang="ts">
useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    // tell useScript how to resolve the third-party script
    use() {
      return { JSConfetti: window.JSConfetti }
    },
  },
})
</script>

Using the third-party script API

Now that we have a way to resolve the third-party script API, we can start using it.

The js-confetti library requires us to instantiate a new instance of the JSConfetti class everytime it's used, the most compatible way to handle this is to wait for the script to load explicitly.

However, we can also make use of proxied functions if we prefer an easier to use API. Note that this will break when switching between pages as new window.JSConfetti() needs to be called between pages.

<script setup lang="ts">
const { onLoaded } = useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    use() {
      return { JSConfetti: window.JSConfetti }
    },
  },
})
onLoaded(({ JSConfetti }) => {
  // using the real API instance
  const confetti = new JSConfetti()
  confetti.addConfetti({ emojis: ['๐ŸŒˆ', 'โšก๏ธ', '๐Ÿ’ฅ', 'โœจ', '๐Ÿ’ซ', '๐ŸŒธ'] })
})
</script>

Congrats! You should see some emojis once the script is loaded in.

However, you'll notice that we have an issue with types here. The addConfetti function is not typed, so we don't get any intellisense or type checking.

Adding types

You can use the generic from the useScriptNpm composable to add types to the script and add global types to the window object.

app.vue
<script setup lang="ts">
export interface JSConfettiApi {
  JSConfetti: { 
    new (): {
      addConfetti: (options?: { emojis: string[] }) => void
    } 
  }
}

declare global {
  interface Window extends JSConfettiApi {}
}

const { onLoaded } = useScriptNpm<JSConfettiApi>({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    use() {
      return { JSConfetti: window.JSConfetti }
    },
  },
})
onMounted(() => {
  onLoaded(({ JSConfetti }) => {
    const confetti = new JSConfetti()
    // fully typed!
    confetti.addConfetti({ emojis: ['๐ŸŒˆ', 'โšก๏ธ', '๐Ÿ’ฅ', 'โœจ', '๐Ÿ’ซ', '๐ŸŒธ'] })
  })
})
</script>

Bonus: Event based script loading

You can delay the loading of the script by using the trigger option. This can be useful if you want to load the script after a certain event or time.

In this example we'll combine the useScriptTriggerElement composable with the useScriptNpm composable to load the script after a certain element is in view.

app.vue
<script setup lang="ts">
const mouseOverEl = ref<HTMLElement>()
const { onLoaded } = useScriptNpm({
  // ..
  scriptOptions: {
    trigger: useScriptTriggerElement({ trigger: 'mouseover', el: mouseOverEl })
  }
})
// ..
onMounted(() => {
  onLoaded(({ JSConfetti }) => {
    const confetti = new JSConfetti()
    confetti.addConfetti({ emojis: ['L', 'O', 'A', 'D', 'E', 'D'] })
  })
})
</script>

<template>
  <div ref="mouseOverEl">
    <h1>Hover over me to load the confetti</h1>
  </div>
</template>

Bonus: Bundling the script

As the script is from NPM and versioned, we can safely bundle it with our application. This will reduce the number of DNS requests needed, improving the performance of our application.

To bundle the script, you can use the bundle option.

app.vue
<script setup lang="ts">
const script = useScriptNpm({
  // ...
  scriptOptions: {
    bundle: true
  }
})
// ..
</script>

You should see the script loaded in from your apps server.

Conclusion

In this tutorial, you learned how to load the js-confetti script using the useScriptNpm registry script.

To learn more about the specific concepts you explored, check out the documentation for Key concepts.