From d6371510153c7613ee4b239ba77e045dabce023c Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 4 Jun 2024 15:49:46 +1000 Subject: [PATCH] feat: carbon ads (#80) --- docs/content/scripts/ads/carbon-ads.md | 205 ++++++++++++++++++ .../third-parties/carbon/nuxt-scripts.vue | 108 +++++++++ src/module.ts | 14 +- src/plugins/transform.ts | 4 +- src/registry.ts | 6 + src/runtime/components/ScriptCarbonAds.vue | 70 ++++++ .../composables/useElementScriptTrigger.ts | 1 - src/runtime/types.ts | 2 +- 8 files changed, 400 insertions(+), 10 deletions(-) create mode 100644 docs/content/scripts/ads/carbon-ads.md create mode 100644 playground/pages/third-parties/carbon/nuxt-scripts.vue create mode 100644 src/runtime/components/ScriptCarbonAds.vue diff --git a/docs/content/scripts/ads/carbon-ads.md b/docs/content/scripts/ads/carbon-ads.md new file mode 100644 index 00000000..4c54ab0d --- /dev/null +++ b/docs/content/scripts/ads/carbon-ads.md @@ -0,0 +1,205 @@ +--- +title: Carbon Ads +description: Show carbon ads in your Nuxt app using a Vue component. +links: + - label: "" + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptCarbonAds.vue + size: xs +--- + +[Carbon Ads](https://www.carbonads.net/) is an ad service that provides a performance friendly way to show ads on your site. + +Nuxt Scripts provides a headless `ScriptCarbonAds` component to embed Carbon Ads in your Nuxt app. + +## ScriptCarbonAds + +The `ScriptCarbonAds` component works differently to other Nuxt Scripts component and does not rely on `useScript`, instead it simply +inserts a script tag into the div of the component on mount. + +By default, the component uses CarbonAds best practices which is to load immediately on mount. You can make use of [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) if you +want to load the ads on a specific event. + +```vue + +``` + +### Handling Ad-blockers + +You can use these hooks to add a fallback when CarbonAds is blocked. + +```vue + +``` + +### Adding UI + +The component renders as headless, meaning there is no inherit styles. If you'd like to customize the look of the ad, you can +use this example from nuxt.com. + +```vue + + + +``` + +### Props + +The `ScriptCarbonAds` component accepts the following props: + +- `serve`: The serve URL provided by Carbon Ads. +- `placement`: The placement ID provided by Carbon Ads. +- `trigger`: The trigger event to load the script. Default is `undefined`. See [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) for more information. + +### Events + +The component emits the script events. + +```ts +const emits = defineEmits<{ + error: [e: string | Event] + load: [] +}>() +``` + +### Slots + +There are a number of slots mapped to the script status that you can use to customize the ad experience. + +- **error**: + The slot is used to display content when the ad fails to load. + +- **awaitingLoad** + The slot is used to display content before the ad script is loaded. + +- **loaded** + The slot is used to display content after the ad script is loaded. + +- **loading** + The slot is used to display content while the ad script is loading. + +```vue + +``` diff --git a/playground/pages/third-parties/carbon/nuxt-scripts.vue b/playground/pages/third-parties/carbon/nuxt-scripts.vue new file mode 100644 index 00000000..4faee79e --- /dev/null +++ b/playground/pages/third-parties/carbon/nuxt-scripts.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/module.ts b/src/module.ts index f7675802..64552648 100644 --- a/src/module.ts +++ b/src/module.ts @@ -22,6 +22,7 @@ import type { NuxtConfigScriptRegistry, NuxtUseScriptInput, NuxtUseScriptOptions, + RegistryScript, RegistryScripts, } from './runtime/types' @@ -128,7 +129,8 @@ export default defineNuxtModule({ const registryScripts = [...scripts] // @ts-expect-error runtime await nuxt.hooks.callHook('scripts:registry', registryScripts) - addImports(registryScripts.map((i) => { + const withComposables = registryScripts.filter(i => !!i.import?.name) as Required[] + addImports(withComposables.map((i) => { return { priority: -1, ...i.import, @@ -136,7 +138,7 @@ export default defineNuxtModule({ })) // compare the registryScripts to the original registry to find new scripts - const newScripts = registryScripts.filter(i => !scripts.some(r => r.import.name === i.import.name)) + const newScripts = withComposables.filter(i => !scripts.some(r => r.import?.name === i.import.name)) // augment types to support the integrations registry extendTypes(name!, async ({ typesPath }) => { @@ -156,9 +158,9 @@ declare module '#nuxt-scripts' { type NuxtUseScriptOptions = Omit interface ScriptRegistry { ${newScripts.map((i) => { - const key = i.import.name.replace('useScript', '') + const key = i.import?.name.replace('useScript', '') const keyLcFirst = key.substring(0, 1).toLowerCase() + key.substring(1) - return ` ${keyLcFirst}?: import('${i.import.from}').${key}Input | [import('${i.import.from}').${key}Input, NuxtUseScriptOptions]` + return ` ${keyLcFirst}?: import('${i.import?.from}').${key}Input | [import('${i.import?.from}').${key}Input, NuxtUseScriptOptions]` }).join('\n')} } }` @@ -176,7 +178,7 @@ ${newScripts.map((i) => { const inits = [] // for global scripts, we can initialise them script away for (const [k, c] of Object.entries(config.registry || {})) { - const importDefinition = registryScripts.find(i => i.import.name === `useScript${k.substring(0, 1).toUpperCase() + k.substring(1)}`) + 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) @@ -206,7 +208,7 @@ ${(config.globals || []).map(g => !Array.isArray(g) const moduleInstallPromises: Map Promise | undefined> = new Map() addBuildPlugin(NuxtScriptBundleTransformer({ - scripts, + scripts: withComposables, defaultBundle: config.defaultScriptOptions?.bundle, moduleDetected(module) { if (nuxt.options.dev && module !== '@nuxt/scripts' && !moduleInstallPromises.has(module) && !hasNuxtModule(module)) diff --git a/src/plugins/transform.ts b/src/plugins/transform.ts index 680bf45f..e4ef6927 100644 --- a/src/plugins/transform.ts +++ b/src/plugins/transform.ts @@ -7,13 +7,13 @@ import type { Node } from 'estree-walker' import { walk } from 'estree-walker' import type { Literal, ObjectExpression, Property, SimpleCallExpression } from 'estree' import type { Input } from 'valibot' -import type { RegistryScripts } from '#nuxt-scripts' +import type { RegistryScript } from '#nuxt-scripts' export interface AssetBundlerTransformerOptions { resolveScript: (src: string) => string moduleDetected?: (module: string) => void defaultBundle?: boolean - scripts?: RegistryScripts + scripts?: Required[] } export function NuxtScriptBundleTransformer(options: AssetBundlerTransformerOptions) { diff --git a/src/registry.ts b/src/registry.ts index b1e9950d..b3218917 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -129,6 +129,12 @@ export const registry: (resolve?: (s: string) => string) => RegistryScripts = (r from: resolve('./runtime/registry/google-adsense'), }, }, + { + label: 'Carbon Ads', + scriptBundling: false, + category: 'ads', + logo: ``, + }, // marketing { label: 'Intercom', diff --git a/src/runtime/components/ScriptCarbonAds.vue b/src/runtime/components/ScriptCarbonAds.vue new file mode 100644 index 00000000..d3a322b3 --- /dev/null +++ b/src/runtime/components/ScriptCarbonAds.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/runtime/composables/useElementScriptTrigger.ts b/src/runtime/composables/useElementScriptTrigger.ts index 4a0d7a5a..9e23e0e8 100644 --- a/src/runtime/composables/useElementScriptTrigger.ts +++ b/src/runtime/composables/useElementScriptTrigger.ts @@ -59,7 +59,6 @@ export function useElementScriptTrigger(options: ElementScriptTriggerOptions): P return new Promise((resolve) => { const _ = useEventListener( typeof el !== 'undefined' ? (el as EventTarget) : document.body, - // @ts-expect-error untyped trigger, () => { resolve() diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 93c86c20..6c458d2a 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -116,7 +116,7 @@ export type RegistryScriptInput = EmptyOptionsSchema } export interface RegistryScript { - import: Import + import?: Import // might just be a component scriptBundling?: false | ((options?: any) => string) label?: string src?: string | false