diff --git a/examples/nuxt3/cypress/e2e/render-story.cy.js b/examples/nuxt3/cypress/e2e/render-story.cy.js index c8e1aef6..6d4a76d7 100644 --- a/examples/nuxt3/cypress/e2e/render-story.cy.js +++ b/examples/nuxt3/cypress/e2e/render-story.cy.js @@ -14,4 +14,9 @@ describe('Story render', () => { cy.visit('/story/components-autoimport-story-vue?variantId=_default') getIframeBody().contains('Meow') }) + + it('should render NuxtLink', () => { + cy.visit('/story/components-basebuttonlink-story-vue?variantId=_default') + cy.get('.__histoire-sandbox a').contains('Hello world') + }) }) diff --git a/packages/histoire-plugin-nuxt/runtime/components.mjs b/packages/histoire-plugin-nuxt/runtime/components.mjs new file mode 100644 index 00000000..af43c650 --- /dev/null +++ b/packages/histoire-plugin-nuxt/runtime/components.mjs @@ -0,0 +1,14 @@ +import { defineComponent, h } from 'vue' + +export const NuxtLink = defineComponent({ + name: 'NuxtLink', + props: { + to: [String, Object], + }, + setup (props, ctx) { + return () => h('a', { + href: typeof props.to === 'string' ? props.to : '#', + ...ctx.attrs, + }, [ctx.slots.default?.()]) + }, +}) diff --git a/packages/histoire-plugin-nuxt/runtime/composables.mjs b/packages/histoire-plugin-nuxt/runtime/composables.mjs new file mode 100644 index 00000000..277b0345 --- /dev/null +++ b/packages/histoire-plugin-nuxt/runtime/composables.mjs @@ -0,0 +1 @@ +export const useNuxtApp = () => ({}) diff --git a/packages/histoire-plugin-nuxt/src/index.ts b/packages/histoire-plugin-nuxt/src/index.ts index baa23cec..3fa8417d 100644 --- a/packages/histoire-plugin-nuxt/src/index.ts +++ b/packages/histoire-plugin-nuxt/src/index.ts @@ -1,3 +1,5 @@ +import { fileURLToPath } from 'node:url' +import { join } from 'node:path' import type { Plugin } from 'histoire' import type { Nuxt } from '@nuxt/schema' import type { UserConfig as ViteConfig } from 'vite' @@ -27,6 +29,7 @@ export function HstNuxt (): Plugin { resolve: { alias: nuxtConfig.viteConfig.resolve.alias, extensions: nuxtConfig.viteConfig.resolve.extensions, + dedupe: nuxtConfig.viteConfig.resolve.dedupe, }, plugins, css: nuxtConfig.viteConfig.css, @@ -65,9 +68,31 @@ async function useNuxtViteConfig () { if (nuxt.options.builder as string !== '@nuxt/vite-builder') { throw new Error(`Histoire only supports Vite bundler, but Nuxt builder is currently set to '${nuxt.options.builder}'.`) } + const runtimeDir = fileURLToPath(new URL('../runtime', import.meta.url)) + nuxt.options.build.templates.push( + { src: join(runtimeDir, 'composables.mjs'), filename: 'histoire/composables.mjs' }, + { src: join(runtimeDir, 'components.mjs'), filename: 'histoire/components.mjs' }, + ) + nuxt.hook('imports:sources', presets => { + const stubbedComposables = ['useNuxtApp'] + const appPreset = presets.find(p => p.from === '#app') + appPreset.imports = appPreset.imports.filter(i => typeof i !== 'string' || !stubbedComposables.includes(i)) + presets.push({ + from: '#build/histoire/composables.mjs', + imports: stubbedComposables, + }) + }) return { viteConfig: await new Promise((resolve) => { nuxt.hook('modules:done', () => { + nuxt.hook('components:extend', (components) => { + for (const name of ['NuxtLink']) { + Object.assign(components.find(c => c.pascalName === name) || {}, { + export: name, + filePath: '#build/histoire/components.mjs', + }) + } + }) nuxt.hook('vite:extendConfig', (config, { isClient }) => { // @ts-ignore if (isClient) resolve({ ...config })