diff --git a/packages/nuxt/src/app/components/nuxt-stubs.ts b/packages/nuxt/src/app/components/nuxt-stubs.ts new file mode 100644 index 0000000000..c2adc56412 --- /dev/null +++ b/packages/nuxt/src/app/components/nuxt-stubs.ts @@ -0,0 +1,17 @@ +import { createError } from '../composables/error' + +function renderStubMessage (name: string) { + throw createError({ + fatal: true, + statusCode: 500, + statusMessage: `${name} is provided by @nuxt/image. Check your console to install it or run 'npx nuxi@latest module add @nuxt/image'` + }) +} + +export const NuxtImg = { + setup: () => renderStubMessage('') +} + +export const NuxtPicture = { + setup: () => renderStubMessage('') +} diff --git a/packages/nuxt/src/components/loader.ts b/packages/nuxt/src/components/loader.ts index b1cea765c6..5e42223bb3 100644 --- a/packages/nuxt/src/components/loader.ts +++ b/packages/nuxt/src/components/loader.ts @@ -46,6 +46,11 @@ export const loaderPlugin = createUnplugin((options: LoaderOptions) => { s.replace(/(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy)?([^'"]*?)["'][\s,]*[^)]*\)/g, (full: string, lazy: string, name: string) => { const component = findComponent(components, name, options.mode) if (component) { + // @ts-expect-error TODO: refactor to nuxi + if (component._internal_install) { + // @ts-expect-error TODO: refactor to nuxi + import('../core/features').then(({ installNuxtModule }) => installNuxtModule(component._internal_install)) + } let identifier = map.get(component) || `__nuxt_component_${num++}` map.set(component, identifier) diff --git a/packages/nuxt/src/core/features.ts b/packages/nuxt/src/core/features.ts index 8920ab2c4d..b23b67444f 100644 --- a/packages/nuxt/src/core/features.ts +++ b/packages/nuxt/src/core/features.ts @@ -1,6 +1,6 @@ import { addDependency } from 'nypm' import { resolvePackageJSON } from 'pkg-types' -import { logger } from '@nuxt/kit' +import { logger, useNuxt } from '@nuxt/kit' import { isCI, provider } from 'std-env' const isStackblitz = provider === 'stackblitz' @@ -11,10 +11,7 @@ export interface EnsurePackageInstalledOptions { prompt?: boolean } -export async function ensurePackageInstalled ( - name: string, - options: EnsurePackageInstalledOptions -) { +async function promptToInstall (name: string, installCommand: () => Promise, options: EnsurePackageInstalledOptions) { if (await resolvePackageJSON(name, { url: options.searchPaths }).catch(() => null)) { return true } @@ -39,10 +36,7 @@ export async function ensurePackageInstalled ( logger.info(`Installing ${name}...`) try { - await addDependency(name, { - cwd: options.rootDir, - dev: true - }) + await installCommand() logger.success(`Installed ${name}`) return true } catch (err) { @@ -50,3 +44,22 @@ export async function ensurePackageInstalled ( return false } } + +// TODO: refactor to Nuxi +const installPrompts = new Set() +export function installNuxtModule (name: string, options?: EnsurePackageInstalledOptions) { + if (installPrompts.has(name)) { return } + installPrompts.add(name) + const nuxt = useNuxt() + return promptToInstall(name, async () => { + const { runCommand } = await import('nuxi') + await runCommand('module', ['add', name, '--cwd', nuxt.options.rootDir]) + }, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options }) +} + +export function ensurePackageInstalled (name: string, options: EnsurePackageInstalledOptions) { + return promptToInstall(name, () => addDependency(name, { + cwd: options.rootDir, + dev: true + }), options) +} diff --git a/packages/nuxt/src/core/nuxt.ts b/packages/nuxt/src/core/nuxt.ts index fde46028e7..40a1a265d1 100644 --- a/packages/nuxt/src/core/nuxt.ts +++ b/packages/nuxt/src/core/nuxt.ts @@ -305,6 +305,18 @@ async function initNuxt (nuxt: Nuxt) { } } + // Add stubs for and + for (const name of ['NuxtImg', 'NuxtPicture']) { + addComponent({ + name, + export: name, + priority: -1, + filePath: resolve(nuxt.options.appDir, 'components/nuxt-stubs'), + // @ts-expect-error TODO: refactor to nuxi + _internal_install: '@nuxt/image' + }) + } + // Add prerender payload support if (!nuxt.options.dev && nuxt.options.experimental.payloadExtraction) { addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))