feat(nuxt): prompt to install nuxt/scripts on usage (#27010)

Co-authored-by: Harlan Wilton <harlan@harlanzw.com>
This commit is contained in:
Daniel Roe 2024-05-03 13:57:28 +01:00 committed by GitHub
parent 7e3b613421
commit 589b4037c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 150 additions and 1 deletions

View File

@ -0,0 +1,102 @@
import type { UseScriptInput } from '@unhead/vue'
import { createError } from './error'
function renderStubMessage (name: string) {
const message = `\`${name}\` is provided by @nuxt/scripts. Check your console to install it or run 'npx nuxi@latest module add @nuxt/scripts' to install it.`
if (import.meta.client) {
throw createError({
fatal: true,
statusCode: 500,
statusMessage: message,
})
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScript<T extends Record<string | symbol, any>> (input: UseScriptInput, options?: Record<string, unknown>) {
renderStubMessage('useScript')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useElementScriptTrigger (...args: unknown[]) {
renderStubMessage('useElementScriptTrigger')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useConsentScriptTrigger (...args: unknown[]) {
renderStubMessage('useConsentScriptTrigger')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useAnalyticsPageEvent (...args: unknown[]) {
renderStubMessage('useAnalyticsPageEvent')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptGoogleAnalytics (...args: unknown[]) {
renderStubMessage('useScriptGoogleAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptPlausibleAnalytics (...args: unknown[]) {
renderStubMessage('useScriptPlausibleAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptCloudflareWebAnalytics (...args: unknown[]) {
renderStubMessage('useScriptCloudflareWebAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptFathomAnalytics (...args: unknown[]) {
renderStubMessage('useScriptFathomAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptMatomoAnalytics (...args: unknown[]) {
renderStubMessage('useScriptMatomoAnalytics')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptGoogleTagManager (...args: unknown[]) {
renderStubMessage('useScriptGoogleTagManager')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptSegment (...args: unknown[]) {
renderStubMessage('useScriptSegment')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptFacebookPixel (...args: unknown[]) {
renderStubMessage('useScriptFacebookPixel')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptXPixel (...args: unknown[]) {
renderStubMessage('useScriptXPixel')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptIntercom (...args: unknown[]) {
renderStubMessage('useScriptIntercom')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptHotjar (...args: unknown[]) {
renderStubMessage('useScriptHotjar')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptStripe (...args: unknown[]) {
renderStubMessage('useScriptStripe')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptLemonSqueezy (...args: unknown[]) {
renderStubMessage('useScriptLemonSqueezy')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptVimeoPlayer (...args: unknown[]) {
renderStubMessage('useScriptVimeoPlayer')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptYouTubeIframe (...args: unknown[]) {
renderStubMessage('useScriptYouTubeIframe')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptGoogleMaps (...args: unknown[]) {
renderStubMessage('useScriptGoogleMaps')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptNpm (...args: unknown[]) {
renderStubMessage('useScriptNpm')
}

View File

@ -20,6 +20,7 @@ import importsModule from '../imports/module'
import { distDir, pkgDir } from '../dirs'
import { version } from '../../package.json'
import { scriptsStubsPreset } from '../imports/presets'
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'
import type { UnctxTransformPluginOptions } from './plugins/unctx'
import { UnctxTransformPlugin } from './plugins/unctx'
@ -125,6 +126,14 @@ async function initNuxt (nuxt: Nuxt) {
}
})
// Prompt to install `@nuxt/scripts` if user has configured it
// @ts-expect-error scripts types are not present as the module is not installed
if (nuxt.options.scripts) {
if (!nuxt.options._modules.some(m => m === '@nuxt/scripts' || m === '@nuxt/scripts-nightly')) {
await import('../core/features').then(({ installNuxtModule }) => installNuxtModule('@nuxt/scripts'))
}
}
// Add plugin normalization plugin
addBuildPlugin(RemovePluginMetadataPlugin(nuxt))
@ -550,6 +559,12 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
}
}
if (!options._modules.some(m => m === '@nuxt/scripts' || m === '@nuxt/scripts-nightly')) {
options.imports = defu(options.imports, {
presets: [scriptsStubsPreset],
})
}
// Nuxt Webpack Builder is currently opt-in
if (options.builder === '@nuxt/webpack-builder') {
if (!await import('./features').then(r => r.ensurePackageInstalled('@nuxt/webpack-builder', {

View File

@ -111,6 +111,33 @@ const granularAppPresets: InlinePreset[] = [
},
]
export const scriptsStubsPreset = {
imports: [
'useConsentScriptTrigger',
'useAnalyticsPageEvent',
'useElementScriptTrigger',
'useScript',
'useScriptGoogleAnalytics',
'useScriptPlausibleAnalytics',
'useScriptCloudflareWebAnalytics',
'useScriptFathomAnalytics',
'useScriptMatomoAnalytics',
'useScriptGoogleTagManager',
'useScriptSegment',
'useScriptFacebookPixel',
'useScriptXPixel',
'useScriptIntercom',
'useScriptHotjar',
'useScriptStripe',
'useScriptLemonSqueezy',
'useScriptVimeoPlayer',
'useScriptYouTubeIframe',
'useScriptGoogleMaps',
'useScriptNpm',
],
from: '#app/composables/script-stubs',
} satisfies InlinePreset
// This is a separate preset as we'll swap these out for import from `vue-router` itself in `pages` module
const routerPreset = defineUnimportPreset({
imports: ['onBeforeRouteLeave', 'onBeforeRouteUpdate'],

View File

@ -1,6 +1,7 @@
import { createUnplugin } from 'unplugin'
import type { Unimport } from 'unimport'
import { normalize } from 'pathe'
import { tryUseNuxt } from '@nuxt/kit'
import type { ImportsOptions } from 'nuxt/schema'
import { isJS, isVue } from '../core/utils'
@ -37,7 +38,11 @@ export const TransformPlugin = createUnplugin(({ ctx, options, sourcemap }: { ct
return
}
const { s } = await ctx.injectImports(code, id, { autoImport: options.autoImport && !isNodeModule })
const { s, imports } = await ctx.injectImports(code, id, { autoImport: options.autoImport && !isNodeModule })
if (imports.some(i => i.from === '#app/composables/script-stubs') && tryUseNuxt()?.options.test === false) {
import('../core/features').then(({ installNuxtModule }) => installNuxtModule('@nuxt/scripts'))
}
if (s.hasChanged()) {
return {
code: s.toString(),