Skip to main content
Getting Started

Tutorial: Load js-confetti

This tutorial loads js-confetti from npm. You'll call it through a proxied function and add types for its browser API.

Background on useScriptNpm()

useScriptNpm() is a registry script built on useScript(). It loads browser-ready package files published to npm.

Most npm packages belong in package.json. Loading one on demand may instead require a dynamic import, a separate chunk, and sometimes build-time transpilation.

useScriptNpm() is useful for an occasional, non-critical browser script that already exposes a global API. Install packages your application uses throughout the codebase as normal dependencies.

The three snippets below load the same file at different abstraction levels.

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

Loading the script

Call useScriptNpm() inside a component:

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

The browser's Network panel should now show the script request.

Resolving the third-party script API

Tell Nuxt Scripts how to resolve the script's client-side API with the use function:

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

The js-confetti library exposes a JSConfetti class. Create an instance after the script loads, then reuse that instance for subsequent calls.

You can wait for the script explicitly or use a proxied function to queue a call until it is ready.

<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>

addConfetti is still untyped, so the editor cannot check its arguments or offer completion.

Adding types

Pass a generic to useScriptNpm() and augment Window with the same API:

app.vue
<script setup lang="ts">
export interface JSConfettiApi {
  JSConfetti: {
    new (config?: { canvas?: HTMLCanvasElement }): {
      addConfetti: (options?: { emojis?: string[] }) => Promise<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 }
    },
  },
})
onLoaded(({ JSConfetti }) => {
  const confetti = new JSConfetti()
  // Checked against JSConfettiApi
  confetti.addConfetti({ emojis: ['🌈', '⚡️', '💥', '', '💫', '🌸'] })
})
</script>

Delay script loading

Use trigger when the script should wait for application state, an event, or a timer.

See the Script Triggers guide for all available options.

Using a ref

A ref loads the script when its value becomes truthy.

app.vue
<script setup lang="ts">
const shouldLoad = ref(false)
const { onLoaded } = useScriptNpm<JSConfettiApi>({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    trigger: shouldLoad,
    use: () => ({ JSConfetti: window.JSConfetti }),
  },
})
onLoaded(({ JSConfetti }) => {
  const confetti = new JSConfetti()
  confetti.addConfetti({ emojis: ['🎉', '🎊', ''] })
})
</script>

<template>
  <button @click="shouldLoad = true">
    Click to load confetti
  </button>
</template>
You can also use a computed ref or getter function: trigger: computed(() => someCondition.value) or trigger: () => shouldLoad.value.

Using element events

Use useScriptTriggerElement() to wait for an element interaction.

app.vue
<script setup lang="ts">
const mouseOverEl = ref<HTMLElement | null>(null)
const { onLoaded } = useScriptNpm<JSConfettiApi>({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    trigger: useScriptTriggerElement({ trigger: 'mouseover', el: mouseOverEl }),
    use: () => ({ JSConfetti: window.JSConfetti }),
  },
})
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>

Bundle the script locally

Nuxt Scripts bundles statically analyzable useScriptNpm() files by default and serves them from /_scripts/assets/. This avoids the initial connection to the package CDN.

Set bundle: false if you want to load the file directly from the configured CDN instead.

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

Without this opt-out, the Network panel shows the script loading from your application's server. See Key Concepts for more on script instances and proxied functions.