Skip to content

Commit 7423527

Browse files
authored
feat!: convert module config globals to object (#127)
1 parent dc79087 commit 7423527

File tree

5 files changed

+132
-82
lines changed

5 files changed

+132
-82
lines changed

docs/content/docs/1.guides/1.script-triggers.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ useScriptMetaPixel({
2828
```ts [Global Script]
2929
export default defineNuxtConfig({
3030
scripts: {
31-
globals: [
32-
['https://example.com/script.js', {
31+
globals: {
32+
myScript: ['https://example.com/script.js', {
3333
// load however you like!
3434
trigger: 'onNuxtReady'
3535
}]
36-
]
36+
}
3737
}
3838
})
3939
```

docs/content/docs/1.guides/4.global.md

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,103 @@ While the `app.head` method in Nuxt Config allows for loading global scripts, it
1010
```ts
1111
export default defineNuxtConfig({
1212
head: {
13-
script: [
14-
{
15-
src: 'https://analytics.com/tracker.js',
16-
async: true,
17-
defer: true,
18-
}
19-
]
13+
script: [ { src: 'https://analytics.com/tracker.js', async: true } ]
2014
}
2115
})
2216
```
2317

2418
A simpler method is now available: directly input the script URL into `scripts.globals`. You can also include optional settings to tailor the script loading process with specific optimizations in mind.
2519

26-
## How it Works
20+
You may consider using global scripts when:
21+
- The script isn't a supported [Registry Script](/docs/api/use-script#registry-script).
22+
- You don't care about interacting with the API provided by the third-party script (e.g. you don't need to use `gtag` from Google Analytics).
23+
- You are interacting with the API provided by the third-party script, but you don't care about type safety.
24+
25+
Otherwise, it's recommended to use [useScript](/docs/api/use-script) to load scripts in a safer way.
26+
27+
## Usage
28+
29+
The `globals` key supports strings, objects and arrays.
30+
31+
**Example: Load a script using just the src**
32+
33+
```ts
34+
export default defineNuxtConfig({
35+
scripts: {
36+
globals: {
37+
myScript: 'https://analytics.com/tracker.js',
38+
}
39+
}
40+
})
41+
```
42+
43+
**Example: Load a script while providing extra script attributes**
44+
45+
```ts
46+
export default defineNuxtConfig({
47+
scripts: {
48+
globals: {
49+
myScript: {
50+
src: 'https://example.com/script.js',
51+
integrity: 'sha256-abc123',
52+
}
53+
}
54+
}
55+
})
56+
```
57+
58+
59+
You can optionally provide the script as an array which allows you to provide [Script Options](/docs/api/use-script#NuxtUseScriptOptions).
60+
61+
```ts
62+
export default defineNuxtConfig({
63+
scripts: {
64+
globals: {
65+
myScript: [
66+
{ src: 'https://example.com/script.js' },
67+
// load the script as part of the hydration process instead of on idle
68+
{ trigger: 'client' }
69+
]
70+
}
71+
}
72+
})
73+
```
2774

28-
The `globals` configuration will be used to create a virtual Nuxt plugin that loads in the script using the `useScript` composable. This allows for the script to be loaded in with best practices in mind.
75+
### Accessing a global script
2976

30-
This also means you can access your script anywhere in your Nuxt app without triggering the script to be loaded again.
77+
All Nuxt Scripts are registered on the `$scripts` Nuxt App property.
3178

32-
For example, if we're loading in a tracking script we can use the `useScript` composable to access the underlying API.
79+
For scripts registered through nuxt.config, type autocompletion is available.
80+
81+
```vue
82+
<script setup lang="ts">
83+
const { $scripts } = useNuxtApp()
84+
$scripts.myScript // { $script, instance }
85+
</script>
86+
```
87+
88+
## How it Works
89+
90+
The `globals` configuration will be used to create a virtual Nuxt plugin that loads in the script using the `useScript` composable.
91+
92+
As `useScript` is being used under the hood, it's important to understand the defaults and behavior of the [useScript](/api/use-script) composable..
3393

3494
::code-group
3595

3696
```ts [nuxt.config.ts]
3797
export default defineNuxtConfig({
3898
scripts: {
39-
globals: [
40-
'https://analytics.com/tracker.js',
41-
]
99+
globals: {
100+
tracker: 'https://analytics.com/tracker.js',
101+
}
42102
}
43103
})
44104
```
45105

46106
```vue [components/Tracking.vue]
47107
<script setup lang="ts">
48108
// This will not trigger the script to be loaded again because of the nuxt.config global script
49-
const { track, $script } = useScript<{ track: (e: string) => void }>('https://analytics.com/tracker.js')
109+
const { track, $script } = useNuxtApp().$scripts.tracker
50110
51111
$script.then(() => {
52112
console.log('Script loaded')
@@ -65,33 +125,3 @@ function trackCustom() {
65125
```
66126

67127
::
68-
69-
## Usage
70-
71-
To load scripts globally, use a configuration-based approach by specifying them in your `nuxt.config.ts`.
72-
73-
```ts
74-
export default defineNuxtConfig({
75-
scripts: {
76-
globals: [
77-
'https://example.com/script.js',
78-
]
79-
}
80-
})
81-
```
82-
83-
You can optionally provide the script as an array which allows you to provide [Script Options](/docs/api/use-script#NuxtUseScriptOptions).
84-
85-
```ts
86-
export default defineNuxtConfig({
87-
scripts: {
88-
globals: [
89-
[
90-
{ src: 'https://example.com/script.js' },
91-
// load the script as part of the hydration process instead of on idle
92-
{ trigger: 'client' }
93-
]
94-
]
95-
}
96-
})
97-
```

src/module.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface ModuleOptions {
4242
/**
4343
* Register scripts that should be loaded globally on all pages.
4444
*/
45-
globals?: (NuxtUseScriptInput | [NuxtUseScriptInput, NuxtUseScriptOptionsSerializable])[]
45+
globals?: Record<string, NuxtUseScriptInput | [NuxtUseScriptInput, NuxtUseScriptOptionsSerializable]>
4646
/** Configure the way scripts assets are exposed */
4747
assets?: {
4848
/**
@@ -123,7 +123,11 @@ export default defineNuxtModule<ModuleOptions>({
123123
nuxt.options.alias['#nuxt-scripts'] = resolve('./runtime/types')
124124
nuxt.options.alias['#nuxt-scripts-utils'] = resolve('./runtime/utils')
125125
nuxt.options.runtimeConfig['nuxt-scripts'] = { version }
126-
nuxt.options.runtimeConfig.public['nuxt-scripts'] = { defaultScriptOptions: config.defaultScriptOptions }
126+
nuxt.options.runtimeConfig.public['nuxt-scripts'] = {
127+
// expose for devtools
128+
version: nuxt.options.dev ? version : undefined,
129+
defaultScriptOptions: config.defaultScriptOptions,
130+
}
127131
addImportsDir([
128132
resolve('./runtime/composables'),
129133
// auto-imports aren't working without this for some reason
@@ -160,6 +164,7 @@ export default defineNuxtModule<ModuleOptions>({
160164
let types = `
161165
declare module '#app' {
162166
interface NuxtApp {
167+
$scripts: Record<${[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].map(k => `'${k}'`).join(' | ')}, Pick<(import('#nuxt-scripts').NuxtAppScript), '$script'> & Record<string, any>>
163168
_scripts: Record<string, (import('#nuxt-scripts').NuxtAppScript)>
164169
}
165170
interface RuntimeNuxtHooks {
@@ -184,7 +189,7 @@ ${newScripts.map((i) => {
184189
return types
185190
})
186191

187-
if (config.globals?.length || Object.keys(config.registry || {}).length) {
192+
if (Object.keys(config.globals || {}).length || Object.keys(config.registry || {}).length) {
188193
// create a virtual plugin
189194
addPluginTemplate({
190195
filename: `modules/${name!.replace('/', '-')}.mjs`,

src/templates.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
import { hash } from 'ohash'
12
import type { ModuleOptions } from './module'
3+
import { logger } from './logger'
24
import type { RegistryScript } from '#nuxt-scripts'
35

46
export function templatePlugin(config: Partial<ModuleOptions>, registry: Required<RegistryScript>[]) {
7+
if (Array.isArray(config.globals)) {
8+
// convert to object
9+
config.globals = Object.fromEntries(config.globals.map(i => [hash(i), i]))
10+
logger.warn('The `globals` array option is deprecated, please convert to an object.')
11+
}
512
const imports = ['useScript', 'defineNuxtPlugin']
613
const inits = []
714
// for global scripts, we can initialise them script away
@@ -13,14 +20,20 @@ export function templatePlugin(config: Partial<ModuleOptions>, registry: Require
1320
const args = (typeof c !== 'object' ? {} : c) || {}
1421
if (c === 'mock')
1522
args.scriptOptions = { trigger: 'manual', skipValidation: true }
16-
inits.push(` ${importDefinition.import.name}(${JSON.stringify(args)})`)
23+
inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args)})`)
24+
}
25+
}
26+
for (const [k, c] of Object.entries(config.globals || {})) {
27+
if (typeof c === 'string') {
28+
inits.push(`const ${k} = useScript(${JSON.stringify({ src: c, key: k })}, { use: () => ({ ${k}: window.${k} }) })`)
29+
}
30+
else if (Array.isArray(c) && c.length === 2) {
31+
inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...(typeof c[0] === 'string' ? { src: c[0] } : c[0]) })}, { ...${JSON.stringify(c[1])}, use: () => ({ ${k}: window.${k} } }) )`)
32+
}
33+
else {
34+
inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...c })}, { use: () => ({ ${k}: window.${k} }) })`)
1735
}
1836
}
19-
const useScriptStatements = (config.globals || []).map(g => typeof g === 'string'
20-
? ` useScript("${g.toString()}")`
21-
: Array.isArray(g) && g.length === 2
22-
? ` useScript(${JSON.stringify(g[0])}, ${JSON.stringify(g[1])} })`
23-
: ` useScript(${JSON.stringify(g)})`)
2437
return [
2538
`import { ${imports.join(', ')} } from '#imports'`,
2639
'',
@@ -29,8 +42,8 @@ export function templatePlugin(config: Partial<ModuleOptions>, registry: Require
2942
` env: { islands: false },`,
3043
` parallel: true,`,
3144
` setup() {`,
32-
...useScriptStatements,
33-
...inits,
45+
...inits.map(i => ` ${i}`),
46+
` return { provide: { $scripts: { ${[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].join(', ')} } } }`,
3447
` }`,
3548
`})`,
3649
].join('\n')

test/unit/templates.test.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe('template plugin file', () => {
55
// global
66
it('empty global', async () => {
77
const res = templatePlugin({
8-
globals: [],
8+
globals: {},
99
registry: {},
1010
}, [])
1111
expect(res).toMatchInlineSnapshot(`
@@ -16,36 +16,37 @@ describe('template plugin file', () => {
1616
env: { islands: false },
1717
parallel: true,
1818
setup() {
19+
return { provide: { $scripts: { } } }
1920
}
2021
})"
2122
`)
2223
})
2324
it('string global', async () => {
2425
const res = templatePlugin({
25-
globals: [
26-
'https://js.stripe.com/v3/',
27-
],
26+
globals: {
27+
stripe: 'https://js.stripe.com/v3/',
28+
},
2829
}, [])
29-
expect(res).toContain('useScript("https://js.stripe.com/v3/")')
30+
expect(res).toContain('const stripe = useScript({"src":"https://js.stripe.com/v3/","key":"stripe"}, { use: () => ({ stripe: window.stripe }) })')
3031
})
3132
it('object global', async () => {
3233
const res = templatePlugin({
33-
globals: [
34-
{
34+
globals: {
35+
stripe: {
3536
async: true,
3637
src: 'https://js.stripe.com/v3/',
3738
key: 'stripe',
3839
defer: true,
3940
referrerpolicy: 'no-referrer',
4041
},
41-
],
42+
},
4243
}, [])
43-
expect(res).toContain('useScript({"async":true,"src":"https://js.stripe.com/v3/","key":"stripe","defer":true,"referrerpolicy":"no-referrer"})')
44+
expect(res).toContain('const stripe = useScript({"key":"stripe","async":true,"src":"https://js.stripe.com/v3/","defer":true,"referrerpolicy":"no-referrer"}, { use: () => ({ stripe: window.stripe }) })')
4445
})
4546
it('array global', async () => {
4647
const res = templatePlugin({
47-
globals: [
48-
[
48+
globals: {
49+
stripe: [
4950
{
5051
async: true,
5152
src: 'https://js.stripe.com/v3/',
@@ -58,29 +59,29 @@ describe('template plugin file', () => {
5859
mode: 'client',
5960
},
6061
],
61-
],
62+
},
6263
}, [])
63-
expect(res).toContain('useScript({"async":true,"src":"https://js.stripe.com/v3/","key":"stripe","defer":true,"referrerpolicy":"no-referrer"}, {"trigger":"onNuxtReady","mode":"client"} })')
64+
expect(res).toContain('const stripe = useScript({"key":"stripe","async":true,"src":"https://js.stripe.com/v3/","defer":true,"referrerpolicy":"no-referrer"}, { ...{"trigger":"onNuxtReady","mode":"client"}, use: () => ({ stripe: window.stripe } }) )')
6465
})
6566
it('mixing global', async () => {
6667
const res = templatePlugin({
67-
globals: [
68-
'https://js.stripe.com/v3/',
69-
{
68+
globals: {
69+
stripe1: 'https://js.stripe.com/v3/',
70+
stripe2: {
7071
async: true,
7172
src: 'https://js.stripe.com/v3/',
7273
key: 'stripe',
7374
defer: true,
7475
referrerpolicy: 'no-referrer',
7576
},
76-
[
77+
stripe3: [
7778
'https://js.stripe.com/v3/',
7879
{
7980
trigger: 'onNuxtReady',
8081
mode: 'client',
8182
},
8283
],
83-
],
84+
},
8485
}, [])
8586
expect(res).toMatchInlineSnapshot(`
8687
"import { useScript, defineNuxtPlugin } from '#imports'
@@ -90,17 +91,18 @@ describe('template plugin file', () => {
9091
env: { islands: false },
9192
parallel: true,
9293
setup() {
93-
useScript("https://js.stripe.com/v3/")
94-
useScript({"async":true,"src":"https://js.stripe.com/v3/","key":"stripe","defer":true,"referrerpolicy":"no-referrer"})
95-
useScript("https://js.stripe.com/v3/", {"trigger":"onNuxtReady","mode":"client"} })
94+
const stripe1 = useScript({"src":"https://js.stripe.com/v3/","key":"stripe1"}, { use: () => ({ stripe1: window.stripe1 }) })
95+
const stripe2 = useScript({"key":"stripe","async":true,"src":"https://js.stripe.com/v3/","defer":true,"referrerpolicy":"no-referrer"}, { use: () => ({ stripe2: window.stripe2 }) })
96+
const stripe3 = useScript({"key":"stripe3","src":"https://js.stripe.com/v3/"}, { ...{"trigger":"onNuxtReady","mode":"client"}, use: () => ({ stripe3: window.stripe3 } }) )
97+
return { provide: { $scripts: { stripe1, stripe2, stripe3 } } }
9698
}
9799
})"
98100
`)
99101
})
100102
// registry
101103
it('registry object', async () => {
102104
const res = templatePlugin({
103-
globals: [],
105+
globals: {},
104106
registry: {
105107
stripe: {
106108
id: 'test',
@@ -117,7 +119,7 @@ describe('template plugin file', () => {
117119
})
118120
it('registry array', async () => {
119121
const res = templatePlugin({
120-
globals: [],
122+
globals: {},
121123
registry: {
122124
stripe: [
123125
{

0 commit comments

Comments
 (0)