Skip to content

Commit 29c83fc

Browse files
authored
refactor(tpc): write only declaration files (#151)
1 parent ea5a7a4 commit 29c83fc

File tree

8 files changed

+173
-127
lines changed

8 files changed

+173
-127
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
"third-party-capital",
6060
"knitwork",
6161
"estree-walker",
62-
"#build/nuxt-scripts/tpc/google-tag-manager",
63-
"#build/nuxt-scripts/tpc/google-analytics",
62+
"#build/modules/nuxt-scripts-gtm",
63+
"#build/modules/nuxt-scripts-ga",
6464
"@vimeo/player",
6565
"esbuild"
6666
]

src/module.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ import type {
2525
RegistryScript,
2626
RegistryScripts,
2727
} from './runtime/types'
28-
import addGoogleAnalyticsRegistry from './tpc/google-analytics'
29-
import addGoogleTagManagerRegistry from './tpc/google-tag-manager'
3028
import checkScripts from './plugins/check-scripts'
3129
import { templatePlugin } from './templates'
30+
import { addTpc } from './tpc/addTpc'
3231

3332
export interface ModuleOptions {
3433
/**
@@ -140,10 +139,7 @@ export default defineNuxtModule<ModuleOptions>({
140139
})
141140

142141
const scripts = registry(resolve)
143-
144-
addGoogleAnalyticsRegistry(scripts)
145-
addGoogleTagManagerRegistry(scripts)
146-
142+
addTpc(scripts)
147143
nuxt.hooks.hook('modules:done', async () => {
148144
const registryScripts = [...scripts]
149145

src/runtime/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import type { LemonSqueezyInput } from './registry/lemon-squeezy'
2222
import type { GoogleAdsenseInput } from './registry/google-adsense'
2323
import type { ClarityInput } from './registry/clarity'
2424
import type { CrispInput } from './registry/crisp'
25-
import type { Input as GoogleTagManagerInput } from '#build/nuxt-scripts/tpc/google-tag-manager'
26-
import type { Input as GoogleAnalyticsInput } from '#build/nuxt-scripts/tpc/google-analytics'
25+
import type { Input as GoogleTagManagerInput } from '#build/modules/nuxt-scripts-gtm'
26+
import type { Input as GoogleAnalyticsInput } from '#build/modules/nuxt-scripts-ga'
2727

2828
export type NuxtUseScriptOptions<T = any> = Omit<UseScriptOptions<T>, 'trigger'> & {
2929
/**

src/tpc/addTpc.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { GooglaAnalyticsData, GoogleAnalytics, GoogleTagManager, GoogleTagManagerData, type Output } from 'third-party-capital'
2+
import { addImports, addTemplate, useNuxt } from '@nuxt/kit'
3+
import type { RegistryScript } from '../runtime/types'
4+
import { extendTypes } from '../kit'
5+
import { generateTpcContent, generateTpcTypes, type ScriptContentOpts } from './utils'
6+
7+
interface TpcDescriptor {
8+
input: ScriptContentOpts
9+
filename: string
10+
registry: RegistryScript
11+
}
12+
13+
const scripts: TpcDescriptor[] = [{
14+
input: {
15+
data: GoogleTagManagerData as Output,
16+
scriptFunctionName: 'useScriptGoogleTagManager',
17+
use: () => {
18+
return { dataLayer: window.dataLayer, google_tag_manager: window.google_tag_manager }
19+
},
20+
stub: ({ fn }) => {
21+
return fn === 'dataLayer' ? [] : undefined
22+
},
23+
tpcKey: 'gtm',
24+
tpcTypeImport: 'GoogleTagManagerApi',
25+
featureDetectionName: 'nuxt-third-parties-gtm',
26+
},
27+
filename: 'nuxt-scripts-gtm',
28+
registry: {
29+
label: 'Google Tag Manager',
30+
category: 'tracking',
31+
logo: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="#8AB4F8" d="m150.262 245.516l-44.437-43.331l95.433-97.454l46.007 45.091z"/><path fill="#4285F4" d="M150.45 53.938L106.176 8.731L9.36 104.629c-12.48 12.48-12.48 32.713 0 45.207l95.36 95.986l45.09-42.182l-72.654-76.407z"/><path fill="#8AB4F8" d="m246.625 105.37l-96-96c-12.494-12.494-32.756-12.494-45.25 0c-12.495 12.495-12.495 32.757 0 45.252l96 96c12.494 12.494 32.756 12.494 45.25 0c12.495-12.495 12.495-32.757 0-45.251"/><circle cx="127.265" cy="224.731" r="31.273" fill="#246FDB"/></svg>`,
32+
scriptBundling(options) {
33+
const data = GoogleTagManager(options)
34+
const mainScript = data.scripts?.find(({ key }) => key === 'gtag')
35+
36+
if (mainScript && 'url' in mainScript && mainScript.url)
37+
return mainScript.url
38+
39+
return false
40+
},
41+
},
42+
}, {
43+
input: {
44+
data: GooglaAnalyticsData as Output,
45+
scriptFunctionName: 'useScriptGoogleAnalytics',
46+
use: () => {
47+
return { dataLayer: window.dataLayer, gtag: window.gtag }
48+
},
49+
// allow dataLayer to be accessed on the server
50+
stub: ({ fn }) => {
51+
return fn === 'dataLayer' ? [] : undefined
52+
},
53+
tpcKey: 'gtag',
54+
tpcTypeImport: 'GoogleAnalyticsApi',
55+
featureDetectionName: 'nuxt-third-parties-ga',
56+
},
57+
filename: 'nuxt-scripts-ga',
58+
registry: {
59+
label: 'Google Analytics',
60+
category: 'tracking',
61+
logo: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="#8AB4F8" d="m150.262 245.516l-44.437-43.331l95.433-97.454l46.007 45.091z"/><path fill="#4285F4" d="M150.45 53.938L106.176 8.731L9.36 104.629c-12.48 12.48-12.48 32.713 0 45.207l95.36 95.986l45.09-42.182l-72.654-76.407z"/><path fill="#8AB4F8" d="m246.625 105.37l-96-96c-12.494-12.494-32.756-12.494-45.25 0c-12.495 12.495-12.495 32.757 0 45.252l96 96c12.494 12.494 32.756 12.494 45.25 0c12.495-12.495 12.495-32.757 0-45.251"/><circle cx="127.265" cy="224.731" r="31.273" fill="#246FDB"/></svg>`,
62+
scriptBundling(options) {
63+
const data = GoogleAnalytics(options)
64+
const mainScript = data.scripts?.find(({ key }) => key === 'gtag')
65+
66+
if (mainScript && 'url' in mainScript && mainScript.url)
67+
return mainScript.url
68+
69+
return false
70+
},
71+
},
72+
}]
73+
74+
export function addTpc(registry: RegistryScript[]) {
75+
const nuxt = useNuxt()
76+
const fileImport = new Map<string, string>()
77+
78+
for (const script of scripts) {
79+
extendTypes(script.filename, async () => await generateTpcTypes(script.input))
80+
81+
const { dst } = addTemplate({
82+
getContents() {
83+
return generateTpcContent(script.input)
84+
},
85+
filename: `modules/${script.filename}.ts`,
86+
})
87+
88+
addImports({
89+
from: dst,
90+
name: 'useScriptGoogleAnalytics',
91+
})
92+
93+
script.registry.import = {
94+
from: dst,
95+
name: script.input.scriptFunctionName,
96+
}
97+
98+
fileImport.set(script.filename, script.input.scriptFunctionName)
99+
100+
registry.push(script.registry)
101+
}
102+
103+
nuxt.hook('prepare:types', async ({ declarations }) => {
104+
declarations.push(`
105+
${Array.from(fileImport).map(([filename, fn]) => `import { ${fn} } from '#build/modules/${filename}'`).join('\n')}
106+
declare module '#imports' {
107+
${Array.from(fileImport).map(([_, fn]) => ` export { ${fn} }`).join('\n')}
108+
}
109+
`)
110+
})
111+
}

src/tpc/google-analytics.ts

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/tpc/google-tag-manager.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/tpc/utils.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import { useNuxt } from '@nuxt/kit'
44
import type { HeadEntryOptions } from '@unhead/vue'
55
import { resolvePath } from 'mlly'
66

7-
export interface ScriptContentOpts {
7+
export interface BaseOpts {
88
data: Output
99
scriptFunctionName: string
1010
tpcTypeImport: string
1111
tpcKey: string
12+
}
13+
export interface ScriptContentOpts extends BaseOpts {
1214
/**
1315
* This will be stringified. The function must be pure.
1416
*/
@@ -23,7 +25,50 @@ export interface ScriptContentOpts {
2325
const HEAD_VAR = '__head'
2426
const INJECTHEAD_CODE = `const ${HEAD_VAR} = injectHead()`
2527

26-
export async function getTpcScriptContent(input: ScriptContentOpts) {
28+
export async function generateTpcTypes(input: BaseOpts) {
29+
const mainScript = input.data.scripts?.find(({ key }) => key === input.tpcKey) as ExternalScript
30+
31+
if (!mainScript)
32+
throw new Error(`no main script found for ${input.tpcKey} in third-party-capital`)
33+
34+
const imports = new Set<string>([
35+
'import type { RegistryScriptInput } from \'#nuxt-scripts\'',
36+
'import type { VueScriptInstance } from \'@unhead/vue\'',
37+
])
38+
39+
imports.add(genImport('#nuxt-scripts-validator', ['object', 'any']))
40+
41+
const chunks: string[] = []
42+
43+
if (input.tpcTypeImport) {
44+
// TPC type import
45+
imports.add(genTypeImport(await resolvePath('third-party-capital', {
46+
url: import.meta.url,
47+
}), [input.tpcTypeImport]))
48+
49+
chunks.push(`
50+
declare global {
51+
interface Window extends ${input.tpcTypeImport} {}
52+
}`)
53+
}
54+
55+
const hasParams = mainScript.params?.length
56+
57+
if (hasParams) {
58+
imports.add(genTypeImport('#nuxt-scripts-validator', ['ObjectSchema', 'AnySchema']))
59+
}
60+
// need schema validation from tpc
61+
chunks.push(`export type Schema = ObjectSchema<{${mainScript.params?.map(p => `${p}: AnySchema`)}}, undefined>`)
62+
chunks.push(`export type Input = RegistryScriptInput<Schema>`)
63+
chunks.push(`export function ${input.scriptFunctionName}<T extends ${input.tpcTypeImport}>(_options?: Input): T & {$script: Promise<T> & VueScriptInstance<T>;}`)
64+
65+
return `
66+
${Array.from(imports).join('\n')}
67+
${chunks.join('\n')}
68+
`
69+
}
70+
71+
export async function generateTpcContent(input: ScriptContentOpts) {
2772
const nuxt = useNuxt()
2873
if (!input.data.scripts)
2974
throw new Error('input.data has no scripts !')

0 commit comments

Comments
 (0)