histoire-dev / histoire

⚡ Fast and beautiful interactive component playgrounds, powered by Vite
https://histoire.dev
MIT License
3.21k stars 191 forks source link

At least one <template> or <script> is required in a single file component #590

Closed samuveth closed 11 months ago

samuveth commented 1 year ago

Related to https://github.com/histoire-dev/histoire/issues/527

I'm still getting this error for 'nuxt-icon' and 'nuxt-icons' plugins. These are both very popular plugins

[vite:vue] At least one <template> or <script> is required in a single file component.
file: /Users/sam/dev/clones/nuxt-tune/node_modules/nuxt-icon/dist/runtime/Icon.vue?nuxt_component=async
node:internal/process/promises:279
            triggerUncaughtException(err, true /* fromPromise */);
            ^

SyntaxError: At least one <template> or <script> is required in a single file component.
    at Object.parse$2 [as parse] (/Users/sam/dev/clones/nuxt-tune/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js:1275:7)
    at createDescriptor (/Users/sam/dev/clones/nuxt-tune/node_modules/@vitejs/plugin-vue/dist/index.cjs:84:43)
    at transformMain (/Users/sam/dev/clones/nuxt-tune/node_modules/@vitejs/plugin-vue/dist/index.cjs:2282:34)
    at Object.transform (/Users/sam/dev/clones/nuxt-tune/node_modules/@vitejs/plugin-vue/dist/index.cjs:2814:16)
    at /Users/sam/dev/clones/nuxt-tune/node_modules/vite-plugin-inspect/dist/index.cjs:932:28
    at Object.plugin2.<computed> (/Users/sam/dev/clones/nuxt-tune/node_modules/vite-plugin-inspect/dist/index.cjs:919:16)
    at file:///Users/sam/dev/clones/nuxt-tune/node_modules/rollup/dist/es/shared/node-entry.js:25518:40 {
  id: '/Users/sam/dev/clones/nuxt-tune/node_modules/nuxt-icon/dist/runtime/Icon.vue?nuxt_component=async',
  plugin: 'vite:vue',
  name: 'RollupError',
  hook: 'transform',
  code: 'PLUGIN_ERROR',
  watchFiles: [
    '/Users/sam/dev/clones/nuxt-tune/node_modules/@histoire/app/dist/bundle-main.js',
    '/Users/sam/dev/clones/nuxt-tune/node_modules/@histoire/app/dist/bundle-sandbox.js',
    '/Users/sam/dev/clones/nuxt-tune/node_modules/@histoire/app/dist/bundled/sandbox.js',
    '/Users/sam/dev/clones/nuxt-tune/node_modules/@histoire/app/dist/app/style/sandbox.css',
    '/Users/sam/dev/clones/nuxt-tune/node_modules/@histoire/app/dist/bundled/index.js',
    '/Users/sam/dev/clones/nuxt-tune/node_modules/@histoire/app/dist/style.css',
    '/Users/sam/dev/clones/nuxt-tune/node_modules/@histoire/app/dist/bundled/style.css',

Originally posted by @samuveth in https://github.com/histoire-dev/histoire/issues/527#issuecomment-1713213570

anoack93 commented 1 year ago

I get the same issue using any of the available icon libraries, which are:

It is enough to add them to the modules list of the nuxt.config to get the At least one <template> or <script> is required in a single file component error.

It can be reproduced by any setup with histoire 0.17.1 and nuxt 3.16.x and using the nuxt and vue plugins. I also added an example component for testing.

My current workaround is to disable the icon module for histoire, which is messy but okay for a short period.

package.json scripts

    "story:dev": "cross-env NUXT_APP_DISABLE_ICON_MODULE=true histoire dev",
    "story:build": "cross-env NUXT_APP_DISABLE_ICON_MODULE=true histoire build",
    "story:preview": "histoire preview

nuxt.config

const modules = [
  '@pinia/nuxt',
]

if (process.env['NUXT_APP_DISABLE_ICON_MODULE'] !== 'true') {
  modules.push('nuxt-icons')
}

export default defineNuxtConfig({
  devtools: { enabled: false },
  modules,
})
anoack93 commented 1 year ago

Here is a minimal example to reproduce the issue:

https://stackblitz.com/edit/nuxt-starter-ppp9v2

anoack93 commented 1 year ago

@Akryum I investigated a bit on this issue and it seems like histoire does not work with the async components exposed by nuxt-icons and svg-sprite libraries.

I have added the nuxt-icon component from library "nuxt-icons" manually to my project and even though there was no build error anymore, it does not display the icons (in nuxt itself it works though) and it does not matter what I render in the component. As long as it is async setup (it is using await in the setup function) nothing is being rendered in histoire (eg. a hardcoded span element with text).

I ensured that that the icons, which I tried to load, exist and were found in the component logic.

I also got this in the browser console

[Vue warn]: Component <Anonymous>: setup function returned a promise, but no <Suspense> boundary was found in the parent component tree. A component with async setup() must be nested in a <Suspense> in order to be rendered. 
  at <NuxtIcon name="search" fill="" > 
  at <DemoComponent icon-name="search" text="DemoComponent" onAwesomeClick=fn  ... > 
  at <RenderStorySubApp>

maybe it would work if histoire puts a suspense tag around the rendered component.

anoack93 commented 1 year ago

This is my current workaround when working with nuxt-icons:

  1. copy Nuxt-Icon to your projects component folder and remove the module
  2. add Suspense as wrapper to every component which uses nuxt-icon
<template>
    <Story title="App/DemoComponent">
      <Suspense>
        <DemoComponent
          icon-name="search"
        />
      </Suspense>
  </Story>
</template>

It is not enough to just add suspense and keep the module as the error would still occure

vsergiu93 commented 1 year ago

From what I saw histoire simply does not work with global components from nuxt which are treated as async components. So is not really about nuxt-icon, it's about async components.

To get this error simply initiate a nuxt project and create a component in components/global then start histoire.

MilanFox commented 1 year ago

Can confirm that it is still happening in the latest Version and isn't fixed together with this issue: https://github.com/histoire-dev/histoire/issues/587#issuecomment-1751680486

OlaAlsaker commented 1 year ago

Happening with me as well, using Nuxt 3.7.4 and Histoire 0.17.2.

TheNaschkatze commented 1 year ago

is also not working with several other modules D: !

tcitworld commented 12 months ago

This seems to happen too with vue-material-design-icons.

alex-lit commented 11 months ago

Try disabling the "@nuxt/content" module, in my case it helped.

MilanFox commented 11 months ago

@alex-lit: nuxt/content isn't installed in the repro @anoack93 provided. Any globally loaded module seems to trigger this issue, not this one specifically.

GerryWilko commented 11 months ago

I have tried to investigate this issue and identify where in the vite transform pipeline this error seems to come from. This error seems to come from the fact that vite is transforming the .vue files which are registered as global components twice.

The following output is from running histoire dev on a Nuxt project with some global components registered.

As you can see in the traces below the first POST TRANSFORM END line shows a global component passing through the vite:vue transform hook within vite.

The second log of POST TRANSFORM ERROR shows the vite:vue hook being run on the already transformed output. In Daniel Roe's answer on Storybook where a similar error seems to be occuring he suggests plugin vue is being loaded multiple times. This would appear to add up with something similar happening here. It doesn't appear to be multiple vite:vue hooks registered though as checking the transform plugins list in the logs below you can see no duplicates.

I'm going to keep looking...

POST TRANSFORM END vite:vue import { defineComponent as _defineComponent } from "vue";
const _sfc_main = /* @__PURE__ */ _defineComponent({
  __name: "JourneyButtons",
  props: {
    buttons: { type: Array, required: true },
    topLevelClass: { type: String, required: true },
    containerDivClass: { type: String, required: true },
    buttonWrapperDivClass: { type: String, required: true }
  },
  emits: ["nextClick", "previousClick"],
  setup(__props, { expose: __expose, emit: __emit }) {
    __expose();
    const emit = __emit;
    function handleClick(button) {
      if (button.type === "next")
        return emit("nextClick");
      if (button.type === "previous")
        return emit("previousClick");
    }
    const __returned__ = { emit, handleClick };
    Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
    return __returned__;
  }
});
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, createElementVNode as _createElementVNode } from "vue";
const _hoisted_1 = ["onClick"];
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return _openBlock(), _createElementBlock(
    "section",
    {
      class: _normalizeClass($props.topLevelClass)
    },
    [
      _createElementVNode(
        "div",
        {
          class: _normalizeClass($props.containerDivClass)
        },
        [
          _createElementVNode(
            "div",
            {
              class: _normalizeClass($props.buttonWrapperDivClass)
            },
            [
              (_openBlock(true), _createElementBlock(
                _Fragment,
                null,
                _renderList($props.buttons, (b, i) => {
                  return _openBlock(), _createElementBlock("button", {
                    key: i,
                    class: _normalizeClass(b.text),
                    onClick: ($event) => $setup.handleClick(b)
                  }, _toDisplayString(b.text), 11, _hoisted_1);
                }),
                128
                /* KEYED_FRAGMENT */
              ))
            ],
            2
            /* CLASS */
          )
        ],
        2
        /* CLASS */
      )
    ],
    2
    /* CLASS */
  );
}
_sfc_main.__hmrId = "1bdbbfa7";
typeof __VUE_HMR_RUNTIME__ !== "undefined" && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main);
import.meta.hot.accept((mod) => {
  if (!mod)
    return;
  const { default: updated, _rerender_only } = mod;
  if (_rerender_only) {
    __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render);
  } else {
    __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated);
  }
});
import _export_sfc from "\0plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__file", "C:/Users/i33672/source/repos/customer-portal/components/content/JourneyButtons.vue"]]);
 [
  'unplugin-formkit',
  'nuxt:layer-aliasing',
  'content-slot',
  'unplugin-vue-i18n',
  'nuxt:client-fallback-auto-id',
  'vite:css',
  'vite:esbuild',
  'vite:json',
  'vite:worker',
  'vite:vue',
  'vite:vue-jsx',
  'replace',
  'nuxt:remove-plugin-metadata',
  'nuxt:chunk-error',
  'nuxt:components:imports',
  'replace',
  'vite:define',
  'vite:css-post',
  'vite:worker-import-meta-url',
  'vite:asset-import-meta-url',
  'vite:dynamic-import-vars',
  'vite:import-glob',
  'nuxt:composable-keys',
  'nuxtjs:i18n-macros-transform',
  'nuxtjs:i18n-resource',
  'nuxt:imports-transform',
  'unctx:transform',
  'nuxt:dev-style-ssr',
  'nuxt:runtime-paths-dep',
  'nuxt:components-loader',
  'nuxt:tree-shake-composables:transform',
  'vite:client-inject',
  'vite:import-analysis'
] Error: why have you done this
    at Object.transform (file:///C:/Users/i33672/source/repos/customer-portal/node_modules/.pnpm/vite@4.5.0/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:44380:95)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async loadAndTransform (file:///C:/Users/i33672/source/repos/customer-portal/node_modules/.pnpm/vite@4.5.0/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:55034:29)
ℹ ✨ optimized dependencies changed. reloading                                                                                                                                                                                                                                    16:27:16
ℹ Vite client warmed up in 1935ms                                                                                                                                                                                                                                                 16:27:16
Failed to resolve dependency: vscode-oniguruma, present in 'optimizeDeps.include'
Failed to resolve dependency: vscode-textmate, present in 'optimizeDeps.include'
Using 8 threads for story collection
Collect stories start all
  ➜  Local:   http://localhost:6006/
  ➜  Network: use --host to expose
✔ Nitro built in 4241 ms                                                                                                                                                                                                                                                    nitro 16:27:20
At least one <template> or <script> is required in a single file component.
POST TRANSFORM ERROR vite:vue import { defineAsyncComponent } from "vue"
export default defineAsyncComponent(() => import("C:/Users/i33672/source/repos/customer-portal/components/content/JourneyButtons.vue").then(r => r.default)) [
  'unplugin-formkit',
  'nuxt:layer-aliasing',
  'content-slot',
  'unplugin-vue-i18n',
  'nuxt:client-fallback-auto-id',
  'histoire:flags',
  'vite:css',
  'vite:esbuild',
  'vite:json',
  'vite:worker',
  'replace',
  'nuxt:remove-plugin-metadata',
  'nuxt:chunk-error',
  'nuxt:components:imports',
  'vite:vue',
  'vite:vue-jsx',
  'replace',
  'histoire-vue-docs-block',
  'vite:define',
  'vite:css-post',
  'vite:worker-import-meta-url',
  'vite:asset-import-meta-url',
  'vite:dynamic-import-vars',
  'vite:import-glob',
  'nuxt:composable-keys',
  'nuxtjs:i18n-macros-transform',
  'nuxtjs:i18n-resource',
  'nuxt:imports-transform',
  'unctx:transform',
  'nuxt:runtime-paths-dep',
  'nuxt:components-loader',
  'histoire-plugin-vue',
  'vite:client-inject',
  'vite:import-analysis'
]
At least one <template> or <script> is required in a single file component. (x2)
At least one <template> or <script> is required in a single file component. (x3)
At least one <template> or <script> is required in a single file component. (x4)
At least one <template> or <script> is required in a single file component. (x5)
At least one <template> or <script> is required in a single file component. (x6)
At least one <template> or <script> is required in a single file component. (x7)
At least one <template> or <script> is required in a single file component. (x8)
At least one <template> or <script> is required in a single file component. (x9)
At least one <template> or <script> is required in a single file component. (x10)
At least one <template> or <script> is required in a single file component. (x11)
At least one <template> or <script> is required in a single file component. (x12)
At least one <template> or <script> is required in a single file component. (x13)
At least one <template> or <script> is required in a single file component. (x14)
At least one <template> or <script> is required in a single file component. (x15)
At least one <template> or <script> is required in a single file component. (x16)
At least one <template> or <script> is required in a single file component. (x17)
At least one <template> or <script> is required in a single file component. (x18)
At least one <template> or <script> is required in a single file component. (x19)
At least one <template> or <script> is required in a single file component. (x20)
At least one <template> or <script> is required in a single file component. (x21)
At least one <template> or <script> is required in a single file component. (x22)
At least one <template> or <script> is required in a single file component. (x23)
At least one <template> or <script> is required in a single file component. (x24)
At least one <template> or <script> is required in a single file component. (x25)
At least one <template> or <script> is required in a single file component. (x26)
At least one <template> or <script> is required in a single file component. (x27)
At least one <template> or <script> is required in a single file component. (x28)
At least one <template> or <script> is required in a single file component. (x29)
At least one <template> or <script> is required in a single file component. (x30)
At least one <template> or <script> is required in a single file component. (x31)
At least one <template> or <script> is required in a single file component. (x32)
At least one <template> or <script> is required in a single file component. (x33)
At least one <template> or <script> is required in a single file component. (x34)
At least one <template> or <script> is required in a single file component. (x35)
At least one <template> or <script> is required in a single file component. (x36)
At least one <template> or <script> is required in a single file component. (x37)
At least one <template> or <script> is required in a single file component. (x38)
At least one <template> or <script> is required in a single file component. (x39)
At least one <template> or <script> is required in a single file component. (x40)
At least one <template> or <script> is required in a single file component. (x41)
At least one <template> or <script> is required in a single file component. (x42)
At least one <template> or <script> is required in a single file component. (x43)
At least one <template> or <script> is required in a single file component. (x44)
POST TRANSFORM ERROR vite:vue import { defineAsyncComponent } from "vue"
export default defineAsyncComponent(() => import("C:/Users/i33672/source/repos/customer-portal/components/content/JourneyButtons.vue").then(r => r.default)) [
  'unplugin-formkit',
  'nuxt:layer-aliasing',
  'content-slot',
  'unplugin-vue-i18n',
  'nuxt:client-fallback-auto-id',
  'histoire:flags',
  'vite:css',
  'vite:esbuild',
  'vite:json',
  'vite:worker',
  'replace',
  'nuxt:remove-plugin-metadata',
  'nuxt:chunk-error',
  'nuxt:components:imports',
  'vite:vue',
  'vite:vue-jsx',
  'replace',
  'histoire-vue-docs-block',
  'vite:define',
  'vite:css-post',
  'vite:worker-import-meta-url',
  'vite:asset-import-meta-url',
  'vite:dynamic-import-vars',
  'vite:import-glob',
  'nuxt:composable-keys',
  'nuxtjs:i18n-macros-transform',
  'nuxtjs:i18n-resource',
  'nuxt:imports-transform',
  'unctx:transform',
  'nuxt:runtime-paths-dep',
  'nuxt:components-loader',
  'histoire-plugin-vue',
  'vite:client-inject',
  'vite:import-analysis'
]
Error while collecting story C:/Users/i33672/source/repos/customer-portal/stories/Woof.story.vue:
SyntaxError: At least one <template> or <script> is required in a single file component.
    at Object.parse$2 [as parse] (C:\Users\i33672\source\repos\customer-portal\node_modules\.pnpm\@vue+compiler-sfc@3.3.8\node_modules\@vue\compiler-sfc\dist\compiler-sfc.cjs.js:1904:7)
    at createDescriptor (C:\Users\i33672\source\repos\customer-portal\node_modules\.pnpm\@vitejs+plugin-vue@4.5.0_vite@4.5.0_vue@3.3.8\node_modules\@vitejs\plugin-vue\dist\index.cjs:86:43)
    at transformMain (C:\Users\i33672\source\repos\customer-portal\node_modules\.pnpm\@vitejs+plugin-vue@4.5.0_vite@4.5.0_vue@3.3.8\node_modules\@vitejs\plugin-vue\dist\index.cjs:2302:34)
    at TransformContext.transform (C:\Users\i33672\source\repos\customer-portal\node_modules\.pnpm\@vitejs+plugin-vue@4.5.0_vite@4.5.0_vue@3.3.8\node_modules\@vitejs\plugin-vue\dist\index.cjs:2848:16)
    at Object.transform (file:///C:/Users/i33672/source/repos/customer-portal/node_modules/.pnpm/vite@4.5.0/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:44353:62)
    at async loadAndTransform (file:///C:/Users/i33672/source/repos/customer-portal/node_modules/.pnpm/vite@4.5.0/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:55034:29)
Collect stories end 2440ms
GerryWilko commented 11 months ago

I believe I have found a potential fix. But I'm a little confused by it...

Changing the hook that @histoire/plugin-nuxt uses to check for the presence of the vue plugins to vite:configResolved and everything starts working fine.

image

(sorry for the rubbish screenshot not sure why its turned out like that)

  1. This shows that no plugins are actually modified by the hook anymore as they are already present.
  2. With the hook changed to configResolved the vite dev server starts and everything seems to work.
  3. If you just remove the hook entirely it no longer errors but the vite dev server never starts.

Very strange. I'm not confortable raising a PR with this just yet as I dont understand why just listening to configResolved but doing nothing seems to make histoire work again. Something for Monday 😄 if anyone notices anything that might explain let me know!

husayt commented 11 months ago

can confirm this is tsill there with latest historie v 0.17.6 and latest nuxt 3.8.2

soulsam480 commented 10 months ago

This is there in the vue plugin too

GerryWilko commented 10 months ago

This is there in the vue plugin too

Most likely this is because your Vue plugin is loaded twice and your code is being transformed twice. I believe following the vanilla histoire setup for a standard Vue 3 project does work. Try setting up a vanilla one and adding some of your vite config piece by piece and see what seems to break it.