Home
Articles Contact
All articles
Astro OpenGraph design.

Passing data to a bundled script in Astro

Do you wish you could use define:vars and import npm packages in your script? This article is for you.

You are writing a client script that calls an action from some user inputs. And now, it turns out you also need to send some data that comes from the frontmatter (server).

If you recognize yourself, you’ve at least tried to use define:vars without success.

Let’s see how we can solve this situation!

If you simply want to be able to use TypeScript in an inline script, there’s a simple solution: use the JSDoc type syntax :

<script>
  /** @type {string} */
  const myVariable = window.SOME_GLOBAL
</script>

Why can’t I just use define:vars?

We all love this directive but just like any other attributes you may add to a script in Astro, it turns it into an inline script!

To quote the documentation:

This is because when Astro bundles a script, it includes and runs the script once even if you include the component containing the script multiple times on one page. define:vars requires a script to rerun with each set of values, so Astro creates an inline script instead.

Template directives reference

Solution

You’ll need a 2nd script to pass the data. There are a few ways to do this (more on this below) but the best way in my opinion is to use a script with type application/json (yes those exist!):

---
import { db } from '~/db'

const data = await db.getUserData()
---

<script
    is:inline
    id="my-data"
    type="application/json"
    set:html={JSON.stringify(data)}
></script>

<script>
    const data = JSON.parse(document.getElementById("my-data")!.innerText)
</script>

You can even share the type if you want to:

---
// Demo.astro
import { db } from '~/db'

export interface Data {
    foo: 'bar'
}

const data: Data = await db.getUserData()
---

<script
    is:inline
    id="my-data"
    type="application/json"
    set:html={JSON.stringify(data)}
></script>

<script>
    import type { Data } from './Demo.astro'
    const data: Data = JSON.parse(document.getElementById("my-data")!.innerText)
</script>

The documentation recommends using data attributes and custom elements . It’s great if you want to pass data that is scoped to the component (ie. as custom elements props). However, I think it has the downside of having to learn a new (verbose) API just to pass some data to your script once (or globally).

No matter what approach you pick:

  • It works great with the Client Router ! If you used a global property on window instead for example, the value could “lag” because only the page contents is swapped
  • Keep in mind that the data you pass from the server to the client has to be serializable, just like when using define:vars.

Big thanks to Armand Philippot , HiDeoo and Oliver Speir for the article reviews!

All articles