Skip to content

Commit 4854b4c

Browse files
harlan-zwclaude
andauthored
docs: improve bundling documentation (#498)
Co-authored-by: Claude <[email protected]>
1 parent 31cd208 commit 4854b4c

File tree

2 files changed

+191
-9
lines changed

2 files changed

+191
-9
lines changed

docs/content/docs/1.guides/2.bundling.md

Lines changed: 127 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,12 @@ useScript('https://example.com/script.js', {
7272
```
7373

7474
```ts [Registry Script]
75-
// Registry script must support bundling
76-
useScriptGoogleAnalytics('https://example.com/script.js', {
77-
bundle: true,
75+
// Registry script bundling using scriptOptions
76+
useScriptGoogleAnalytics({
77+
id: 'GA_MEASUREMENT_ID',
78+
scriptOptions: {
79+
bundle: true
80+
}
7881
})
7982
```
8083
::
@@ -93,15 +96,130 @@ export default defineNuxtConfig({
9396
})
9497
```
9598

96-
### Limitations of Bundling
99+
### Build-time vs Runtime Behavior
100+
101+
Understanding when bundling happens and how it affects runtime behavior is crucial for effective usage.
102+
103+
#### Build-time Processing
104+
105+
Bundling occurs during the build phase through static code analysis:
106+
107+
```ts
108+
// ✅ Bundled at build-time (static values)
109+
useScript('https://example.com/script.js', { bundle: true })
110+
111+
// ❌ Cannot be bundled (dynamic values)
112+
const scriptUrl = computed(() => getScriptUrl())
113+
useScript(scriptUrl, { bundle: dynamic.value })
114+
```
115+
116+
#### Runtime Behavior
97117

98-
While many scripts can be bundled, there are exceptions you need to be aware of.
118+
At runtime, bundled scripts behave differently:
99119

100-
For instance, certain scripts:
101-
- Require tracking all user interactions for security reasons, like fraud detection (e.g., Stripe).
102-
- Must be served directly from their original source to function properly (e.g., Fathom Analytics).
120+
```ts
121+
// Original code
122+
useScript('https://example.com/script.js', { bundle: true })
103123

104-
Scripts from known registries are pre-configured to either allow or disallow bundling. For your own scripts, you'll need to decide whether bundling is appropriate on a case-by-case basis.
124+
// After build transformation
125+
useScript('/_scripts/abc123.js', {})
126+
```
127+
128+
**Important**: Once bundled, you lose access to the original URL at runtime. If you need the original URL for tracking or analytics, store it separately.
129+
130+
#### Static URL Requirements
131+
132+
For bundling to work, the transformer requires **completely static values**:
133+
134+
::code-group
135+
136+
```ts [✅ Valid for Bundling]
137+
// Static string literals
138+
useScript('https://cdn.example.com/lib.js', { bundle: true })
139+
140+
// Static template literals (no variables)
141+
useScript(`https://cdn.example.com/lib.js`, { bundle: true })
142+
143+
// Constants defined at module level
144+
const SCRIPT_URL = 'https://cdn.example.com/lib.js'
145+
useScript(SCRIPT_URL, { bundle: true })
146+
```
147+
148+
```ts [❌ Cannot be Bundled]
149+
// Runtime variables
150+
const url = getScriptUrl()
151+
useScript(url, { bundle: true })
152+
153+
// Computed values
154+
const scriptUrl = computed(() => `https://cdn.example.com/${version.value}.js`)
155+
useScript(scriptUrl, { bundle: true })
156+
157+
// Environment variables at runtime
158+
useScript(process.env.SCRIPT_URL, { bundle: true })
159+
160+
// Props or reactive values
161+
useScript(props.scriptUrl, { bundle: true })
162+
```
163+
164+
::
165+
166+
#### Manual Injection Patterns
167+
168+
When automatic bundling isn't possible, you can manually inject bundled scripts:
169+
170+
```ts [Manual Bundling Workaround]
171+
// 1. Bundle during build with static URL
172+
const staticScript = useScript('https://cdn.example.com/static.js', {
173+
bundle: true,
174+
trigger: 'manual' // Don't auto-load
175+
})
176+
177+
// 2. Conditionally load based on runtime logic
178+
function loadScript() {
179+
if (shouldLoadScript.value) {
180+
staticScript.load()
181+
}
182+
}
183+
184+
// 3. Alternative: Use multiple static configurations
185+
const scriptVariants = {
186+
dev: useScript('https://cdn.example.com/dev.js', { bundle: true, trigger: 'manual' }),
187+
prod: useScript('https://cdn.example.com/prod.js', { bundle: true, trigger: 'manual' })
188+
}
189+
190+
// Load appropriate variant
191+
const currentScript = computed(() =>
192+
isDev ? scriptVariants.dev : scriptVariants.prod
193+
)
194+
```
195+
196+
#### Working with Dynamic URLs
197+
198+
For truly dynamic scenarios, consider these patterns:
199+
200+
```ts [Dynamic URL Strategies]
201+
// Option 1: Pre-bundle known variants
202+
const analytics = {
203+
google: useScript('https://www.googletagmanager.com/gtag/js', { bundle: true }),
204+
plausible: useScript('https://plausible.io/js/script.js', { bundle: true })
205+
}
206+
207+
// Option 2: Fallback to runtime loading
208+
function loadDynamicScript(url: string) {
209+
// This won't be bundled, but will work at runtime
210+
return useScript(url, {
211+
bundle: false, // Explicitly disable
212+
trigger: 'manual'
213+
})
214+
}
215+
216+
// Option 3: Use server-side bundling
217+
// Store script content in your bundle and inject manually
218+
const { $script } = useNuxtApp()
219+
$script.add({
220+
innerHTML: await $fetch('/api/dynamic-script-content'),
221+
})
222+
```
105223

106224
### Change Asset Behavior
107225

test/unit/transform.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,70 @@ describe('nuxtScriptTransformer', () => {
147147
expect(code).toMatchInlineSnapshot(`"const instance = useScriptFathomAnalytics({ src: '/_scripts/custom.js.js' }, )"`)
148148
})
149149

150+
it('registry script with scriptOptions.bundle - correct usage', async () => {
151+
vi.mocked(hash).mockImplementationOnce(() => 'analytics')
152+
const code = await transform(
153+
`const instance = useScriptGoogleAnalytics({
154+
id: 'GA_MEASUREMENT_ID',
155+
scriptOptions: {
156+
bundle: true
157+
}
158+
})`,
159+
{
160+
defaultBundle: false,
161+
scripts: [
162+
{
163+
scriptBundling() {
164+
return 'https://www.googletagmanager.com/gtag/js'
165+
},
166+
import: {
167+
name: 'useScriptGoogleAnalytics',
168+
from: '',
169+
},
170+
},
171+
],
172+
},
173+
)
174+
expect(code).toMatchInlineSnapshot(`
175+
"const instance = useScriptGoogleAnalytics({ scriptInput: { src: '/_scripts/analytics.js' },
176+
id: 'GA_MEASUREMENT_ID',
177+
scriptOptions: {
178+
bundle: true
179+
}
180+
})"
181+
`)
182+
})
183+
184+
it('registry script with top-level bundle also transforms', async () => {
185+
vi.mocked(hash).mockImplementationOnce(() => 'gtag/js')
186+
const code = await transform(
187+
`const instance = useScriptGoogleAnalytics({
188+
id: 'GA_MEASUREMENT_ID'
189+
}, {
190+
bundle: true
191+
})`,
192+
{
193+
defaultBundle: false,
194+
scripts: [
195+
{
196+
scriptBundling() {
197+
return 'https://www.googletagmanager.com/gtag/js'
198+
},
199+
import: {
200+
name: 'useScriptGoogleAnalytics',
201+
from: '',
202+
},
203+
},
204+
],
205+
},
206+
)
207+
expect(code).toMatchInlineSnapshot(`
208+
"const instance = useScriptGoogleAnalytics({ scriptInput: { src: '/_scripts/gtag/js.js' },
209+
id: 'GA_MEASUREMENT_ID'
210+
}, )"
211+
`)
212+
})
213+
150214
it('static src integration is transformed - opt-in', async () => {
151215
const code = await transform(
152216
`const instance = useScriptFathomAnalytics({ site: '123' }, { bundle: true, })`,

0 commit comments

Comments
 (0)