Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
samuveth opened this issue Sep 11, 2023 · 16 comments
Closed

Comments

@samuveth
Copy link

samuveth commented Sep 11, 2023

Related to #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
Copy link

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

  • nuxt-icons
  • nuxt-icon
  • @nuxtjs/svg-sprite

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
Copy link

Here is a minimal example to reproduce the issue:

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

@anoack93
Copy link

anoack93 commented Sep 20, 2023

@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
Copy link

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
Copy link

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
Copy link

Can confirm that it is still happening in the latest Version and isn't fixed together with this issue:
#587 (comment)

@OlaAlsaker
Copy link

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

@TheNaschkatze
Copy link

is also not working with several other modules D: !

@tcitworld
Copy link

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

@alex-lit
Copy link

alex-lit commented Nov 21, 2023

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

@MilanFox
Copy link

@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
Copy link
Contributor

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
Copy link
Contributor

GerryWilko commented Nov 24, 2023

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
Copy link

husayt commented Nov 27, 2023

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

@soulsam480
Copy link

This is there in the vue plugin too

@GerryWilko
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests