Skip to content

Commit

Permalink
fix: generate globals plugin correctly
Browse files Browse the repository at this point in the history
Fixes #115, #121
  • Loading branch information
harlan-zw committed Jul 3, 2024
1 parent db571cf commit 1594f67
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 35 deletions.
42 changes: 9 additions & 33 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ import { registry } from './registry'
import type {
NuxtConfigScriptRegistry,
NuxtUseScriptInput,
NuxtUseScriptOptions,
NuxtUseScriptOptionsSerializable,
RegistryScript,
RegistryScripts,
} from './runtime/types'
import addGoogleAnalyticsRegistry from './tpc/google-analytics'
import addGoogleTagManagerRegistry from './tpc/google-tag-manager'
import checkScripts from './plugins/check-scripts'
import { templatePlugin } from './templates'

export interface ModuleOptions {
/**
Expand All @@ -37,11 +38,11 @@ export interface ModuleOptions {
/**
* Default options for scripts.
*/
defaultScriptOptions?: NuxtUseScriptOptions
defaultScriptOptions?: NuxtUseScriptOptionsSerializable
/**
* Register scripts that should be loaded globally on all pages.
*/
globals?: (NuxtUseScriptInput | [NuxtUseScriptInput, NuxtUseScriptOptions])[]
globals?: (NuxtUseScriptInput | [NuxtUseScriptInput, NuxtUseScriptOptionsSerializable])[]
/** Configure the way scripts assets are exposed */
assets?: {
/**
Expand Down Expand Up @@ -143,16 +144,16 @@ export default defineNuxtModule<ModuleOptions>({
const registryScripts = [...scripts]

await nuxt.hooks.callHook('scripts:registry', registryScripts)
const withComposables = registryScripts.filter(i => !!i.import?.name) as Required<RegistryScript>[]
addImports(withComposables.map((i) => {
const registryScriptsWithImport = registryScripts.filter(i => !!i.import?.name) as Required<RegistryScript>[]
addImports(registryScriptsWithImport.map((i) => {
return {
priority: -1,
...i.import,
}
}))

// compare the registryScripts to the original registry to find new scripts
const newScripts = withComposables.filter(i => !scripts.some(r => r.import?.name === i.import.name))
const newScripts = registryScriptsWithImport.filter(i => !scripts.some(r => r.import?.name === i.import.name))

// augment types to support the integrations registry
extendTypes(name!, async ({ typesPath }) => {
Expand Down Expand Up @@ -188,32 +189,7 @@ ${newScripts.map((i) => {
addPluginTemplate({
filename: `modules/${name!.replace('/', '-')}.mjs`,
getContents() {
const imports = ['useScript', 'defineNuxtPlugin']
const inits = []
// for global scripts, we can initialise them script away
for (const [k, c] of Object.entries(config.registry || {})) {
const importDefinition = withComposables.find(i => i.import.name === `useScript${k.substring(0, 1).toUpperCase() + k.substring(1)}`)
if (importDefinition) {
// title case
imports.unshift(importDefinition.import.name)
const args = (typeof c !== 'object' ? {} : c) || {}
if (c === 'mock')
args.scriptOptions = { trigger: 'manual', skipValidation: true }
inits.push(`${importDefinition.import.name}(${JSON.stringify(args)});`)
}
}
return `import { ${imports.join(', ')} } from '#imports'
export default defineNuxtPlugin({
name: "${name}:init",
setup() {
${(config.globals || []).map(g => !Array.isArray(g)
? ` useScript("${g.toString()}")`
: g.length === 2
? ` useScript(${JSON.stringify(g[0])}, ${JSON.stringify(g[1])} })`
: ` useScript(${JSON.stringify(g[0])})`).join('\n')}
${inits.join('\n ')}
}
})`
return templatePlugin(config, registryScriptsWithImport)
},
})
}
Expand All @@ -222,7 +198,7 @@ ${(config.globals || []).map(g => !Array.isArray(g)

const moduleInstallPromises: Map<string, () => Promise<boolean> | undefined> = new Map()
addBuildPlugin(NuxtScriptBundleTransformer({
scripts: withComposables,
scripts: registryScriptsWithImport,
defaultBundle: config.defaultScriptOptions?.bundle,
moduleDetected(module) {
if (nuxt.options.dev && module !== '@nuxt/scripts' && !moduleInstallPromises.has(module) && !hasNuxtModule(module))
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export type NuxtUseScriptOptions<T = any> = Omit<UseScriptOptions<T>, 'trigger'>
skipValidation?: boolean
}

export type NuxtUseScriptIntegrationOptions = Omit<NuxtUseScriptOptions, 'use'>
export type NuxtUseScriptOptionsSerializable = Omit<NuxtUseScriptOptions, 'use' | 'skipValidation' | 'stub' | 'trigger' | 'eventContext' | 'beforeInit'> & { trigger?: 'client' | 'server' | 'onNuxtReady' }

export type NuxtUseScriptInput = UseScriptInput

Expand Down Expand Up @@ -105,7 +105,7 @@ export interface ScriptRegistry {
[key: `${string}-npm`]: NpmInput
}

export type NuxtConfigScriptRegistryEntry<T> = true | 'mock' | T | [T, NuxtUseScriptOptions<T>]
export type NuxtConfigScriptRegistryEntry<T> = true | 'mock' | T | [T, NuxtUseScriptOptionsSerializable]
export type NuxtConfigScriptRegistry<T extends keyof ScriptRegistry = keyof ScriptRegistry> = Partial<
Record<T, NuxtConfigScriptRegistryEntry<ScriptRegistry[T]>>
>
Expand Down
37 changes: 37 additions & 0 deletions src/templates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ModuleOptions } from './module'
import type { RegistryScript } from '#nuxt-scripts'

export function templatePlugin(config: Partial<ModuleOptions>, registry: Required<RegistryScript>[]) {
const imports = ['useScript', 'defineNuxtPlugin']
const inits = []
// for global scripts, we can initialise them script away
for (const [k, c] of Object.entries(config.registry || {})) {
const importDefinition = registry.find(i => i.import.name === `useScript${k.substring(0, 1).toUpperCase() + k.substring(1)}`)
if (importDefinition) {
// title case
imports.unshift(importDefinition.import.name)
const args = (typeof c !== 'object' ? {} : c) || {}
if (c === 'mock')
args.scriptOptions = { trigger: 'manual', skipValidation: true }
inits.push(` ${importDefinition.import.name}(${JSON.stringify(args)})`)
}
}
const useScriptStatements = (config.globals || []).map(g => typeof g === 'string'
? ` useScript("${g.toString()}")`
: Array.isArray(g) && g.length === 2
? ` useScript(${JSON.stringify(g[0])}, ${JSON.stringify(g[1])} })`
: ` useScript(${JSON.stringify(g)})`)
return [
`import { ${imports.join(', ')} } from '#imports'`,
'',
`export default defineNuxtPlugin({`,
` name: "scripts:init",`,
` env: { islands: false },`,
` parallel: true,`,
` setup() {`,
...useScriptStatements,
...inits,
` }`,
`})`,
].join('\n')
}
140 changes: 140 additions & 0 deletions test/unit/templates.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { describe, it, expect } from 'vitest'
import { templatePlugin } from '../../src/templates'

describe('template plugin file', () => {
// global
it('empty global', async () => {
const res = templatePlugin({
globals: [],
registry: {},
}, [])
expect(res).toMatchInlineSnapshot(`
"import { useScript, defineNuxtPlugin } from '#imports'
export default defineNuxtPlugin({
name: "scripts:init",
env: { islands: false },
parallel: true,
setup() {
}
})"
`)
})
it('string global', async () => {
const res = templatePlugin({
globals: [
'https://js.stripe.com/v3/',
],
}, [])
expect(res).toContain('useScript("https://js.stripe.com/v3/")')
})
it('object global', async () => {
const res = templatePlugin({
globals: [
{
async: true,
src: 'https://js.stripe.com/v3/',
key: 'stripe',
defer: true,
referrerpolicy: 'no-referrer',
},
],
}, [])
expect(res).toContain('useScript({"async":true,"src":"https://js.stripe.com/v3/","key":"stripe","defer":true,"referrerpolicy":"no-referrer"})')
})
it('array global', async () => {
const res = templatePlugin({
globals: [
[
{
async: true,
src: 'https://js.stripe.com/v3/',
key: 'stripe',
defer: true,
referrerpolicy: 'no-referrer',
},
{
trigger: 'onNuxtReady',
mode: 'client',
},
],
],
}, [])
expect(res).toContain('useScript({"async":true,"src":"https://js.stripe.com/v3/","key":"stripe","defer":true,"referrerpolicy":"no-referrer"}, {"trigger":"onNuxtReady","mode":"client"} })')
})
it('mixing global', async () => {
const res = templatePlugin({
globals: [
'https://js.stripe.com/v3/',
{
async: true,
src: 'https://js.stripe.com/v3/',
key: 'stripe',
defer: true,
referrerpolicy: 'no-referrer',
},
[
'https://js.stripe.com/v3/',
{
trigger: 'onNuxtReady',
mode: 'client',
},
],
],
}, [])
expect(res).toMatchInlineSnapshot(`
"import { useScript, defineNuxtPlugin } from '#imports'
export default defineNuxtPlugin({
name: "scripts:init",
env: { islands: false },
parallel: true,
setup() {
useScript("https://js.stripe.com/v3/")
useScript({"async":true,"src":"https://js.stripe.com/v3/","key":"stripe","defer":true,"referrerpolicy":"no-referrer"})
useScript("https://js.stripe.com/v3/", {"trigger":"onNuxtReady","mode":"client"} })
}
})"
`)
})
// registry
it('registry object', async () => {
const res = templatePlugin({
globals: [],
registry: {
stripe: {
id: 'test',
},
},
}, [
{
import: {
name: 'useScriptStripe',
},
},
])
expect(res).toContain('useScriptStripe({"id":"test"})')
})
it('registry array', async () => {
const res = templatePlugin({
globals: [],
registry: {
stripe: [
{
id: 'test',
},
{
trigger: 'onNuxtReady',
},
],
},
}, [
{
import: {
name: 'useScriptStripe',
},
},
])
expect(res).toContain('useScriptStripe([{"id":"test"},{"trigger":"onNuxtReady"}])')
})
})

0 comments on commit 1594f67

Please sign in to comment.