Skip to content

Commit

Permalink
refactor(tpc): write only declaration files (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
huang-julien authored Jul 15, 2024
1 parent ea5a7a4 commit 29c83fc
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 127 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
"third-party-capital",
"knitwork",
"estree-walker",
"#build/nuxt-scripts/tpc/google-tag-manager",
"#build/nuxt-scripts/tpc/google-analytics",
"#build/modules/nuxt-scripts-gtm",
"#build/modules/nuxt-scripts-ga",
"@vimeo/player",
"esbuild"
]
Expand Down
8 changes: 2 additions & 6 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ import type {
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'
import { addTpc } from './tpc/addTpc'

export interface ModuleOptions {
/**
Expand Down Expand Up @@ -140,10 +139,7 @@ export default defineNuxtModule<ModuleOptions>({
})

const scripts = registry(resolve)

addGoogleAnalyticsRegistry(scripts)
addGoogleTagManagerRegistry(scripts)

addTpc(scripts)
nuxt.hooks.hook('modules:done', async () => {
const registryScripts = [...scripts]

Expand Down
4 changes: 2 additions & 2 deletions src/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import type { LemonSqueezyInput } from './registry/lemon-squeezy'
import type { GoogleAdsenseInput } from './registry/google-adsense'
import type { ClarityInput } from './registry/clarity'
import type { CrispInput } from './registry/crisp'
import type { Input as GoogleTagManagerInput } from '#build/nuxt-scripts/tpc/google-tag-manager'
import type { Input as GoogleAnalyticsInput } from '#build/nuxt-scripts/tpc/google-analytics'
import type { Input as GoogleTagManagerInput } from '#build/modules/nuxt-scripts-gtm'
import type { Input as GoogleAnalyticsInput } from '#build/modules/nuxt-scripts-ga'

export type NuxtUseScriptOptions<T = any> = Omit<UseScriptOptions<T>, 'trigger'> & {
/**
Expand Down
111 changes: 111 additions & 0 deletions src/tpc/addTpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { GooglaAnalyticsData, GoogleAnalytics, GoogleTagManager, GoogleTagManagerData, type Output } from 'third-party-capital'
import { addImports, addTemplate, useNuxt } from '@nuxt/kit'
import type { RegistryScript } from '../runtime/types'
import { extendTypes } from '../kit'
import { generateTpcContent, generateTpcTypes, type ScriptContentOpts } from './utils'

interface TpcDescriptor {
input: ScriptContentOpts
filename: string
registry: RegistryScript
}

const scripts: TpcDescriptor[] = [{
input: {
data: GoogleTagManagerData as Output,
scriptFunctionName: 'useScriptGoogleTagManager',
use: () => {
return { dataLayer: window.dataLayer, google_tag_manager: window.google_tag_manager }
},
stub: ({ fn }) => {
return fn === 'dataLayer' ? [] : undefined
},
tpcKey: 'gtm',
tpcTypeImport: 'GoogleTagManagerApi',
featureDetectionName: 'nuxt-third-parties-gtm',
},
filename: 'nuxt-scripts-gtm',
registry: {
label: 'Google Tag Manager',
category: 'tracking',
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>`,
scriptBundling(options) {
const data = GoogleTagManager(options)
const mainScript = data.scripts?.find(({ key }) => key === 'gtag')

if (mainScript && 'url' in mainScript && mainScript.url)
return mainScript.url

return false
},
},
}, {
input: {
data: GooglaAnalyticsData as Output,
scriptFunctionName: 'useScriptGoogleAnalytics',
use: () => {
return { dataLayer: window.dataLayer, gtag: window.gtag }
},
// allow dataLayer to be accessed on the server
stub: ({ fn }) => {
return fn === 'dataLayer' ? [] : undefined
},
tpcKey: 'gtag',
tpcTypeImport: 'GoogleAnalyticsApi',
featureDetectionName: 'nuxt-third-parties-ga',
},
filename: 'nuxt-scripts-ga',
registry: {
label: 'Google Analytics',
category: 'tracking',
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>`,
scriptBundling(options) {
const data = GoogleAnalytics(options)
const mainScript = data.scripts?.find(({ key }) => key === 'gtag')

if (mainScript && 'url' in mainScript && mainScript.url)
return mainScript.url

return false
},
},
}]

export function addTpc(registry: RegistryScript[]) {
const nuxt = useNuxt()
const fileImport = new Map<string, string>()

for (const script of scripts) {
extendTypes(script.filename, async () => await generateTpcTypes(script.input))

const { dst } = addTemplate({
getContents() {
return generateTpcContent(script.input)
},
filename: `modules/${script.filename}.ts`,
})

addImports({
from: dst,
name: 'useScriptGoogleAnalytics',
})

script.registry.import = {
from: dst,
name: script.input.scriptFunctionName,
}

fileImport.set(script.filename, script.input.scriptFunctionName)

registry.push(script.registry)
}

nuxt.hook('prepare:types', async ({ declarations }) => {
declarations.push(`
${Array.from(fileImport).map(([filename, fn]) => `import { ${fn} } from '#build/modules/${filename}'`).join('\n')}
declare module '#imports' {
${Array.from(fileImport).map(([_, fn]) => ` export { ${fn} }`).join('\n')}
}
`)
})
}
52 changes: 0 additions & 52 deletions src/tpc/google-analytics.ts

This file was deleted.

54 changes: 0 additions & 54 deletions src/tpc/google-tag-manager.ts

This file was deleted.

49 changes: 47 additions & 2 deletions src/tpc/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { useNuxt } from '@nuxt/kit'
import type { HeadEntryOptions } from '@unhead/vue'
import { resolvePath } from 'mlly'

export interface ScriptContentOpts {
export interface BaseOpts {
data: Output
scriptFunctionName: string
tpcTypeImport: string
tpcKey: string
}
export interface ScriptContentOpts extends BaseOpts {
/**
* This will be stringified. The function must be pure.
*/
Expand All @@ -23,7 +25,50 @@ export interface ScriptContentOpts {
const HEAD_VAR = '__head'
const INJECTHEAD_CODE = `const ${HEAD_VAR} = injectHead()`

export async function getTpcScriptContent(input: ScriptContentOpts) {
export async function generateTpcTypes(input: BaseOpts) {
const mainScript = input.data.scripts?.find(({ key }) => key === input.tpcKey) as ExternalScript

if (!mainScript)
throw new Error(`no main script found for ${input.tpcKey} in third-party-capital`)

const imports = new Set<string>([
'import type { RegistryScriptInput } from \'#nuxt-scripts\'',
'import type { VueScriptInstance } from \'@unhead/vue\'',
])

imports.add(genImport('#nuxt-scripts-validator', ['object', 'any']))

const chunks: string[] = []

if (input.tpcTypeImport) {
// TPC type import
imports.add(genTypeImport(await resolvePath('third-party-capital', {
url: import.meta.url,
}), [input.tpcTypeImport]))

chunks.push(`
declare global {
interface Window extends ${input.tpcTypeImport} {}
}`)
}

const hasParams = mainScript.params?.length

if (hasParams) {
imports.add(genTypeImport('#nuxt-scripts-validator', ['ObjectSchema', 'AnySchema']))
}
// need schema validation from tpc
chunks.push(`export type Schema = ObjectSchema<{${mainScript.params?.map(p => `${p}: AnySchema`)}}, undefined>`)
chunks.push(`export type Input = RegistryScriptInput<Schema>`)
chunks.push(`export function ${input.scriptFunctionName}<T extends ${input.tpcTypeImport}>(_options?: Input): T & {$script: Promise<T> & VueScriptInstance<T>;}`)

return `
${Array.from(imports).join('\n')}
${chunks.join('\n')}
`
}

export async function generateTpcContent(input: ScriptContentOpts) {
const nuxt = useNuxt()
if (!input.data.scripts)
throw new Error('input.data has no scripts !')
Expand Down
Loading

0 comments on commit 29c83fc

Please sign in to comment.