Skip to content

Commit

Permalink
feat: support bundling scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw committed Mar 17, 2024
1 parent ada596b commit 98570fe
Show file tree
Hide file tree
Showing 12 changed files with 548 additions and 133 deletions.
69 changes: 65 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Plus Nuxt goodies:

- 🕵️ `createConsentTrigger` - Create a script trigger that you can resolve with a ref or a promise.
- 🪵 DevTools integration - see all your loaded scripts with function logs
- ⏬ Serve third-party scripts from your own server

## Installation

Expand Down Expand Up @@ -54,6 +55,66 @@ Nuxt Scripts was created to solve these issues and more with the goal of making

## Usage

### Getting Started

To start using Nuxt Scripts, you can use the `useScript` composable to load your third-party scripts.

```ts
useScript('https://cdn.jsdelivr.net/npm/js-confetti@latest/dist/js-confetti.browser.js')
```

See the Unhead [useScript](https://unhead.unjs.io/usage/composables/use-script) guide for next steps.

### Bundling Scripts

Bundling scripts can allow you to serve them from your own server, improving privacy and performance. It
can also help to get around ad blockers and other privacy tools when you need a script to load.

You can opt-in to have your scripts bundled by using the `assetStrategy` option. As this is
analyzed at build time, you must define it statically.

```ts
useScript('https://cdn.jsdelivr.net/npm/js-confetti@latest/dist/js-confetti.browser.js', {
assetStrategy: 'bundle'
})
// js-confetti.browser.js will be downloaded and bundled with your app as a static asset
```

### Sending Page Events

When using tracking scripts, it's common to send an event when the page changes. Due to Nuxt's head implementation being
async, the page title is not always available on route change immediately.

`useAnalyticsPageEvent` solves this by providing you with the page title and path when they change.

```ts
useAnalyticsPageEvent(({ title, path }) => {
// triggered on route change
gtag('event', 'page_view', {
page_title: title,
page_location: 'https://example.com',
page_path: path
})
})
```

### Privacy and Cookie Consent

Nuxt Scripts provides a `createConsentTrigger` composable that allows you to load scripts based on the user's consent.

```ts
const agreedToCookies = ref(false)
useScript('https://www.google-analytics.com/analytics.js', {
// will be loaded in when the ref is true
trigger: createConsentTrigger({
honourDoNotTrack: true, // optional, disabled by default
consent: agreedToCookies
})
})
```

## API

### `useScript`

Please see the [useScript](https://unhead.unjs.io/usage/composables/use-script) documentation.
Expand Down Expand Up @@ -88,17 +149,17 @@ useScript('https://www.google-analytics.com/analytics.js', {
})
```

### `useTrackedPage`
### `useAnalyticsPageEvent`

It's common when using tracking scripts to send an event when the page changes. Due to Nuxt's head implementation being
async, it's not possible to send the page title on route change.

`useTrackedPage` solves this by providing you with the page title and path when they change.
`useAnalyticsPageEvent` solves this by providing you with the page title and path when they change.

You can either provide a function to call on page change or use the ref that's returned.

```ts
useTrackedPage(({ title, path }) => {
useAnalyticsPageEvent(({ title, path }) => {
gtag('event', 'page_view', {
page_title: title,
page_location: 'https://example.com',
Expand All @@ -108,7 +169,7 @@ useTrackedPage(({ title, path }) => {
```

```ts
const trackedPage = useTrackedPage()
const trackedPage = useAnalyticsPageEvent()
watch(trackedPage, ({ title, path }) => {
gtag('event', 'page_view', {
page_title: title,
Expand Down
30 changes: 20 additions & 10 deletions client/app.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts" setup>
import { onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'
import { isRelative } from 'ufo'
import { appFetch, devtools } from '~/composables/rpc'
import { reactive, ref } from '#imports'
import { loadShiki } from '~/composables/shiki'
Expand Down Expand Up @@ -41,20 +42,27 @@ function bytesToSize(bytes: number) {
return `${Number.parseFloat((bytes / 1024 ** i).toFixed(2))} ${sizes[i]}`
}
function syncScripts(_scripts: any[]) {
scripts.value = { ..._scripts }
// check if the script size has been set, if not set it
for (const key in _scripts) {
if (!scriptSizes[key]) {
getScriptSize(_scripts[key].src).then((size) => {
scriptSizes[key] = bytesToSize(size)
}).catch(() => {
scriptSizes[key] = 0
})
}
}
}
onDevtoolsClientConnected(async (client) => {
appFetch.value = client.host.app.$fetch
devtools.value = client.devtools
client.host.nuxt.hooks.hook('scripts:updated', (ctx) => {
scripts.value = { ...ctx.scripts }
// check if the script size has been set, if not set it
for (const key in ctx.scripts) {
if (!scriptSizes[key]) {
getScriptSize(ctx.scripts[key].src).then((size) => {
scriptSizes[key] = bytesToSize(size)
})
}
}
syncScripts(ctx.scripts)
})
syncScripts(client.host.nuxt._scripts)
})
function humanFriendlyTimestamp(timestamp: number) {
// use Intl.DateTimeFormat to format the timestamp, we only need the time aspect
Expand All @@ -67,7 +75,9 @@ function humanFriendlyTimestamp(timestamp: number) {
}
function urlToOrigin(url: string) {
return new URL(url).origin
if (isRelative(url, { acceptRelative: true }))
return new URL(url).origin
return url
}
</script>

Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import antfu from '@antfu/eslint-config'
export default antfu({
rules: {
'no-use-before-define': 'off',
'node/prefer-global/buffer': 'off',
},
})
20 changes: 15 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,31 @@
"dependencies": {
"@nuxt/devtools-kit": "^1.0.8",
"@nuxt/devtools-ui-kit": "^1.0.8",
"@nuxt/kit": "^3.10.3",
"@nuxt/kit": "^3.11.0",
"consola": "^3.2.3",
"defu": "^6.1.4",
"estree-walker": "^3.0.3",
"h3": "^1.11.1",
"magic-string": "^0.30.8",
"ofetch": "^1.3.3",
"ohash": "^1.1.3",
"pathe": "^1.1.2",
"pkg-types": "^1.0.3",
"shiki": "^1.2.0",
"sirv": "^2.0.4"
"sirv": "^2.0.4",
"ufo": "^1.5.1",
"unplugin": "^1.10.0",
"unstorage": "^1.10.2"
},
"devDependencies": {
"@antfu/eslint-config": "2.8.1",
"@antfu/eslint-config": "2.8.3",
"@nuxt/devtools-ui-kit": "^1.0.8",
"@nuxt/module-builder": "^0.5.5",
"@nuxt/test-utils": "3.11.0",
"@nuxt/test-utils": "3.12.0",
"@nuxt/ui": "2.14.2",
"bumpp": "^9.4.0",
"eslint": "8.57.0",
"nuxt": "^3.10.3",
"nuxt": "^3.11.0",
"nuxt-icon": "^0.6.9",
"playwright": "^1.42.1",
"typescript": "^5.4.2",
Expand Down
Loading

0 comments on commit 98570fe

Please sign in to comment.