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

Provide information how to use Quasar with Storybook #11654

Open
Seanitzel opened this issue Dec 12, 2021 · 15 comments
Open

Provide information how to use Quasar with Storybook #11654

Seanitzel opened this issue Dec 12, 2021 · 15 comments

Comments

@Seanitzel
Copy link

Seanitzel commented Dec 12, 2021

Is your feature request related to a problem? Please describe.
For the past few weeks, I tried multiple times to make my Quasar project work with storybook, to no avail.

It works when no sass variables are used, but storybook fails to compile when it detects that sass variables are used anywhere, with an error about the variable being undefined.

I tried defining them and anything else i could think of, use various webpack sass loaders, import the variables from quasar, and other things that i no longer remember, none worked.

Describe the solution you'd like
Any solution to how I can use Quasar with storybook properly.

Describe alternatives you've considered
There are no alternatives to storybook that i'm aware of.
Storybook is an important of the project's build process.

Additional context
I created a reproduction repo of the error i'm getting, based on the Quasar 2 + storybook repo that someone built but fails with the same error when using sass variables.

Edit to be clear:
The error message is
SassError: Undefined variable.

Reproducttion:
https://github.com/Seanitzel/Quasar-V2-Storybook

@freakzlike
Copy link

What is the error message you are facing?

Maybe this issue #11683?

@Seanitzel
Copy link
Author

The error message is
SassError: Undefined variable.

And I don't think it is related to the issue you tagged

@jclaveau
Copy link
Contributor

jclaveau commented Feb 28, 2022

Having the same issue, as a workaround, I re-imported it in my component using variables:

<style lang="scss">
// I have to add it for Storybook to avoid SassError: Undefined variable. background-color: rgba($primary-200, 0.8);
// TODO Configure Storybook properly
@import "src/css/quasar.variables.scss";
...

I'll let you know, if I find a good config.
This https://dev.to/yemolai/using-storybook-with-quasar-3090 looks inspiring (sharing webpack configuration between Storybook and Quasar) but it seems to remake what css configuration https://quasar.dev/quasar-cli/quasar-conf-js#property-css is meant for.

@jclaveau
Copy link
Contributor

jclaveau commented Mar 3, 2022

@arsenijesavic
Copy link

@jclaveau what is the correct way to set the spacing variables? I'm struggling with how to transfer this file to storybook

@54mu3l
Copy link

54mu3l commented May 19, 2023

I have Quasar + Vite + Storybook running with the following preview.ts:

import type { Preview } from '@storybook/vue3';

import '@quasar/extras/mdi-v7/mdi-v7.css';

import 'quasar/dist/quasar.css';

import { setup } from '@storybook/vue3';
import { Quasar } from 'quasar';

setup((app) => {
  app.use(Quasar, {});
});

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
};

export default preview;

This works, except my custom styles from app.sass and quasar.variables.sass won't get loaded.

@jclaveau
Copy link
Contributor

jclaveau commented Jul 3, 2023

A way to use Storybook 7 with Quasar / Vite and SCSS support

Use Vite 4

  • Update Quasar to use Vite 4
npm remove @intlify/vite-plugin-vue-i18n # outdated
npm install @quasar/app-vite@v2.0.0-alpha.11
npm update @quasar/vite-plugin
npm update @vitejs/plugin-vue

Install Storybook

Extract the Vite config generated by Quasar (based on quasar.config.js) and inject it into Storybook's Vite

  • in .storybook create the file quasar-config-result.js with the following content (Kept in JS as Quasar is written in JS)
import { QuasarConfFile } from '@quasar/app-vite/lib/quasar-config-file'
import { getQuasarCtx } from '@quasar/app-vite/lib/utils/get-quasar-ctx'
import { extensionRunner } from '@quasar/app-vite/lib/app-extension/extensions-runner'

// This code is taken from @quasar/app/bin/quasar-inspect
export async function getQuasarConfig (mode='spa', debug=true, cmd='dev', hostname=9000, port=9000) {

  // Requires adding
  // // https://github.com/evanw/esbuild/issues/1921#issuecomment-1197938703
  // + "\nimport { createRequire } from 'module';const require = createRequire(import.meta.url);"
  // to apps/quasar/node_modules/@quasar/app-vite/lib/quasar-config-file.js / createEsbuildConfig () / esbuilConfig.banner.js

  const ctx = getQuasarCtx({
    mode: mode,
    target: mode === 'cordova' || mode === 'capacitor'
      ? 'android'
      : void 0,
    debug: debug,
    dev: cmd === 'dev',
    prod: cmd === 'build'
    // vueDevtools?
  })


  await extensionRunner.registerExtensions(ctx)


  const quasarConfFile = new QuasarConfFile({
    ctx,
    port: port,
    host: hostname,
    watch: undefined
  })

  const quasarConf = await quasarConfFile.read()
  if (quasarConf.error !== void 0) {
    throw new Error(quasarConf.error)
  }
  // console.log('quasarConf', quasarConf)

  const modeConfig = (await import(`@quasar/app-vite/lib/modes/${mode}/${mode}-config.js`))?.modeConfig

  const cfgEntries = []
  let threadList = Object.keys(modeConfig)

  for (const name of threadList) {
    cfgEntries.push({
      name,
      object: await modeConfig[ name ](quasarConf)
    })
  }

  return cfgEntries
}
  • As mentioned in the previous script, we need to patch /node_modules/@quasar/app-vite/lib/quasar-config-file.js to support Esbuild to ESM so I added a patch in /patches/@quasar+app-vite+2.0.0-alpha.11.patch to use with https://github.com/ds300/patch-package like
diff --git a/node_modules/@quasar/app-vite/lib/quasar-config-file.js b/node_modules/@quasar/app-vite/lib/quasar-config-file.js
index 904363d..9645fa6 100644
--- a/node_modules/@quasar/app-vite/lib/quasar-config-file.js
+++ b/node_modules/@quasar/app-vite/lib/quasar-config-file.js
@@ -67,12 +67,28 @@ function createEsbuildConfig () {
     },
     banner: {
       js: quasarConfigBanner
+      // https://github.com/evanw/esbuild/issues/1921#issuecomment-1197938703
+      + `
+      // This is probably due to the fact we use the alpha version of this package.
+      // TODO remove it once it is released
+      import { createRequire } from 'node:module';
+      const cjsRequire = createRequire(import.meta.url);
+      const require = (path) => {
+        const matches = path.match(/^(.+)\.mjs$/)
+
+        if (matches !== null) {
+          path = matches[1] + '.js'
+        }
+
+        return cjsRequire(path);
+      }
+      `
     },
     define: quasarEsbuildInjectReplacementsDefine,
     resolveExtensions: [ appPaths.quasarConfigOutputFormat === 'esm' ? '.mjs' : '.cjs', '.js', '.mts', '.ts', '.json' ],
     entryPoints: [ appPaths.quasarConfigFilename ],
     outfile: tempFile,
-    plugins: [ quasarEsbuildInjectReplacementsPlugin ]
+    plugins: [ quasarEsbuildInjectReplacementsPlugin ],
   }
 }
  • In /.storybook/main.ts inject the extracted config (This could probably be more complete but it works like that for now)
import type { StorybookConfig } from '@storybook/vue3-vite';
import type { Options } from '@storybook/types';
import type { InlineConfig, PluginOption } from 'vite';

import { mergeConfig } from 'vite'
import { getQuasarConfig } from './quasar-config-result';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/vue3-vite',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
  async viteFinal(config: InlineConfig, options: Options): Promise<Record<string, any>> {
    // https://github.com/storybookjs/builder-vite#migration-from-webpack--cra
    const quasarConfig = await getQuasarConfig()
    const quasarViteConfig = quasarConfig[0].object
    // console.log('quasarConfig', quasarViteConfig.server)
    // console.log('storybook Vite config', JSON.stringify(config, null, 2))

    // Quasar's Vite Plugins
    const quasarVitePluginNames = quasarViteConfig.plugins.map((pluginConfig: PluginOption) => {
      if (pluginConfig instanceof Promise) {
        throw new Error('Promise is not supported for Quasar\'s vite config merge')
      }
      if (Array.isArray(pluginConfig)) {
        // TODO are these config required for stories rendering?
        console.warn('Arrays of Vite PluginOption are not supported for Quasar\'s vite config merge', JSON.stringify(pluginConfig, null, 2))
      }
      else if (pluginConfig !== false) {
        return pluginConfig.name
      }
    })

    // We must remove Vue plugins from Storybook before injecting Quasar's ones
    config.plugins = config.plugins.filter((pluginConfig: PluginOption) => {
      if (pluginConfig instanceof Promise) {
        throw new Error('Promise is not supported for Quasar\'s vite config merge')
      }
      if (pluginConfig instanceof Array) {
        throw new Error('Arrays of Vite PluginOption are not supported for Quasar\'s vite config merge')
      }
      return !pluginConfig || pluginConfig.name == null || ! quasarVitePluginNames.includes(pluginConfig.name)
    })
    config.plugins = [...config.plugins, ...quasarViteConfig.plugins]

    const updatedConfig: Record<string, any> =  mergeConfig(config, {
      resolve: {
        alias: {
          ...quasarViteConfig.resolve.alias
        },
      },
      server: {
        ...quasarViteConfig.server
      },
      // Avoid error Failed to load url /sb-preview/runtime.js (resolved id: /sb-preview/runtime.js). Does the file exist?
      // [vite]: Rollup failed to resolve import "/./sb-preview/runtime.js" from "/home/jean/dev/Hippocast/prototype/apps/quasar/iframe.html".
      // This is most likely unintended because it can break your application at runtime.
      // If you do want to externalize this module explicitly add it to
      // `build.rollupOptions.external`
      build: {
        rollupOptions: {
          external: [
            /sb-preview\/runtime.js$/,
          ]
        }
      }
    });

    return updatedConfig
  },
};
export default config;

Boot Storybook's Vue app like the Quasar one

  • Create a .storybook/client-entry-storybook.js containing
/**
 * This file is a copy of .quasar/client-entry.js Vue app object injected.
 * As client-entry.js is generated from template, you may have to patch it
 * if you change quasar.conf.js
 */

// When quasar.config.js changes, css files must be manually imported here as in apps/quasar/.quasar/client-entry.js
import '@quasar/extras/roboto-font/roboto-font.css'
import '@quasar/extras/material-icons/material-icons.css'

// We load Quasar stylesheet file
import 'quasar/dist/quasar.sass'
import 'src/css/app.scss'

console.info('[Quasar] Running SPA for Storybook.')

const publicPath = ``


export async function start ({ app, router, store }, bootFiles) {

  let hasRedirected = false
  const getRedirectUrl = url => {
    try { return router.resolve(url).href }
    catch (err) {}

    return Object(url) === url
      ? null
      : url
  }

  const redirect = url => {
    hasRedirected = true

    if (typeof url === 'string' && /^https?:\/\//.test(url)) {
      window.location.href = url
      return
    }

    const href = getRedirectUrl(url)

    // continue if we didn't fail to resolve the url
    if (href !== null) {
      window.location.href = href
      // window.location.reload()
    }
  }

  const urlPath = window.location.href.replace(window.location.origin, '')

  for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
    try {
      await bootFiles[i]({
        app,
        router,
        store,
        ssrContext: null,
        redirect,
        urlPath,
        publicPath
      })
    }
    catch (err) {
      if (err && err.url) {
        redirect(err.url)
        return
      }

      console.error('[Quasar] boot error:', err)
      return
    }
  }

  if (hasRedirected === true) {
    return
  }

  // App mounting is handled by Storybook
  // app.mount('#q-app')
}
  • Then change .storybook/preview.ts to use Quasar upon Storybook's Vue instance
import type { Preview } from '@storybook/vue3';
import { setup } from '@storybook/vue3';
import type { App } from 'vue';

// Customize Storybook's style
import './storybook.scss'

import quasarUserOptions from '../.quasar/quasar-user-options' // lang / iconset
import { start } from './client-entry-storybook.js'

type ModuleType = {
  default: null | Function
}


import createStore from '../src/stores/index'
import createRouter from '../src/router/index'
import { Quasar } from 'quasar'
import { markRaw } from 'vue'

(async () => {
  // Chromatic doesn't support top level awaits
  // Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14" + 2 overrides)

  // The store and router instanciation must be done here as Storybook's setup() fn doesn't support async
  const store = typeof createStore === 'function'
  ? await createStore({})
  : createStore

  // Those are required by createRouter()
  global.process = {
    env: {
      VUE_ROUTER_MODE: 'hash',          // Use a routing through hash to avoid ovewriting Storybook url parts (e.g. http://192.168.1.8:6006/iframe.html?globals=backgrounds.grid:!true;backgrounds.value:!hex(F8F8F8)&args=#/)
      VUE_ROUTER_BASE: '/iframe.html',  // The url of Storybook's preview iframe
    },
  }


  const router = markRaw(
  typeof createRouter === 'function'
  ? await createRouter({store})
  : createRouter
  )

  setup((app: App) => {
    app.use(Quasar, quasarUserOptions)
    app.use(store)
    store.use(({ store }) => { store.router = router })
    // router must be used before boot files to avoid "[Vue warn]: Failed to resolve component: router-view" in SB
    // TODO ensure this works always. Does vueRouterMode: 'history' in config impact it?
    app.use(router)

    return Promise.all([
      // When quasar.config.js changes, boot files must be manually imported here as in apps/quasar/.quasar/client-entry.js
      // import('../src/boot/i18n'),
      // import('boot/axios'),
    ])
    .then((bootFiles: ModuleType[]) => {

      const boot = bootFiles
        .map((entry: ModuleType) => {
          return entry.default
        })
        .filter(entry => entry instanceof Function)

      start({app, router, store}, boot)
    })
  });
})()


const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
};

export default preview;
  • As you can see I added a .storybook/storybook.scss to change the error display of Storybook errors and for the story of the App component below
// This makes SB stories still visible in case of js exception
.sb-wrapper {
  position: relative !important;
  max-height: 300px;
  overflow-y: auto;
}

// Avoids infinite width producing errors on Chromatic due to the 25 millions pixels threshold passed
.fixed, .fixed-full, .fullscreen, .fixed-center, .fixed-bottom, .fixed-left, .fixed-right, .fixed-top, .fixed-top-left, .fixed-top-right, .fixed-bottom-left, .fixed-bottom-right {
  position: absolute !important;
}

.storybook-anti-oversize-wrapper {
  max-width: 2000px;
  overflow-y: auto;
  border: 1px solid #888;
}

Add some sample stories

  • Remove Storybook's examples
  • Create a story for the Quasar EssentialLink example component /src/stories/EssentialLink.stories.ts containing
import type { Meta, StoryObj } from '@storybook/vue3';

import EssentialLink from '../components/EssentialLink.vue';

// More on how to set up stories at: https://storybook.js.org/docs/vue/writing-stories/introduction
const meta = {
  title: 'Example/EssentialLink',
  component: EssentialLink,
  // This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
  tags: ['autodocs'],
  argTypes: {
    iconColor: { control: 'select', options: ['primary', 'secondary', 'accent'] },
    // onClick: { action: 'clicked' },
  },
  args: { iconColor: 'accent' }, // default value
} satisfies Meta<typeof EssentialLink>;

export default meta;
type Story = StoryObj<typeof meta>;
/*
 *👇 Render functions are a framework specific feature to allow you control on how the component renders.
 * See https://storybook.js.org/docs/vue/api/csf
 * to learn how to use render functions.
 */
export const Simple: Story = {
  args: {
    // iconColor: 'primary',
    title: 'My essential link',
    caption: 'caption',
    link: 'https://www.google.com',
    icon: 'warning',
  },
};

export const LongText: Story = {
  args: {
    title: 'Aenean neque urna, aliquam in nunc ac, volutpat finibus lectus. Etiam quis orci ut est blandit vestibulum id ut mauris. Cras consequat erat in elit convallis tempor. Duis quis nibh accumsan nibh congue vestibulum sed et nisl. Aliquam imperdiet suscipit magna, a vulputate lorem facilisis vel. Donec facilisis vehicula suscipit. Donec scelerisque vel sapien et posuere. Nunc sit amet lacinia metus. Vivamus egestas nulla in lectus fermentum varius. ',
    caption: 'caption',
    link: 'https://www.google.com',
    icon: 'warning',
  },
};
  • A modified a bit this component to control the icon color from the Story to check sass variables support
<template>
  <q-item
    clickable
    tag="a"
    target="_blank"
    :href="link"
  >
    <q-item-section
      v-if="icon"
      avatar
    >
      <q-icon :name="icon" :color="iconColor"/>
    </q-item-section>

    <q-item-section>
      <q-item-label>{{ title }}</q-item-label>
      <q-item-label caption>{{ caption }}</q-item-label>
    </q-item-section>
  </q-item>
</template>

<script setup lang="ts">
export interface EssentialLinkProps {
  title: string;
  caption?: string;
  link?: string;
  icon?: string;
  iconColor?: string;
}
withDefaults(defineProps<EssentialLinkProps>(), {
  caption: '',
  link: '#',
  icon: '',
  iconColor: '',
});
</script>
  • Add a Story for the App component src/stories/App.stories.ts. The route control allows navigation
import type { Meta, StoryObj } from '@storybook/vue3';
import { useArgs } from '@storybook/preview-api';

import App from '../App.vue';

// This is definitelly not the best way to add custom args
// TODO investigate https://stackoverflow.com/a/72223811/2714285 and find a proper way to do this
type AppArgs = {
  route: string;
};
type InputPropOverrides = {
  args: AppArgs;
};

const meta = {
  title: 'App',
  component: App,
  args: {
    route: '/',
  },
  render: (args) => {
    const [_, updateArgs] = useArgs();
    window.location.hash = args.route

    return {
      components: { App },
      data: () => {
        return { args }
      },
      watch:{
        $route (to) {
          if (to.fullPath != args.route) {
            updateArgs({ route: to.fullPath })
          }
        },
      },
      template: `
        <div class="storybook-anti-oversize-wrapper">
          <App />
        </div>
      `,
    }
  },
} satisfies Meta<typeof App> & InputPropOverrides;

export default meta;
type Story = StoryObj<typeof meta>;
export const AppSimple: Story & InputPropOverrides = {
  args: {
    route: '/',
  },
};

Track .quasar changes to update Storybook config if needed

  • Comment .quasar in .gitignore

Configure Chromatic

  • Create a .github/workflows/chromatic.yml file containing (you'll need to set your working directory):
name: 'Chromatic Publish'
# https://github.com/chromaui/action
# https://www.chromatic.com/docs/github-actions

on: pull_request # Avoids passing Chromatic's snapshots threshold

# https://stackoverflow.com/a/72408109/2714285
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  test:
    # Disable chromatic push during Renovate's updates
    if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' }}
    defaults:
      run:
        working-directory: apps/quasar

    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0 # Required to retrieve git history

      # https://github.com/renovatebot/renovate/issues/7716#issuecomment-734391360
      - name: Read Node.js version from .nvmrc
        run: echo "NODE_VERSION=$(cat ../../.nvmrc)" >> $GITHUB_OUTPUT
        id: nvm

      - name: Dump Node.js version from .nvmrc
        run: echo "NODE_VERSION=$(cat ../../.nvmrc)"

      # https://github.com/actions/setup-node#caching-global-packages-data
      - uses: actions/setup-node@v3
        with:
          # https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#multiple-operating-systems-and-architectures
          node-version: ${{ steps.nvm.outputs.NODE_VERSION }}

      - name: npm install
        run: npm install

      - uses: chromaui/action@v1
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
          token: ${{ secrets.GITHUB_TOKEN }}
          workingDir: apps/quasar
  • Add the following line to your package.json
  "scripts": {
    // ...
    "chromatic": "npx chromatic --exit-zero-on-changes",
    "postinstall": "patch-package"
  },

Conclusion

This way I'm able to use Storybook 7 with Quasar 2.12.1 and Typescript but

  • IMHO a lot of this code belong to the Quasar team like
    • Extracting Vite config / generating the viteFinal method
    • Booting the Quasar app based on quasar.config.js (boot files and scss imports)
    • Esbuild ESM imports
    • Also, auto-configuring Storybook from Quasar CLI would be incredible
  • Here I only cover Vite support (without being sure it's enough). One year ago I tried with Webpack, it worked but was real pain to achieve. But globally it's the same work.

Hoping supporting Storybook from the CLI (Webpack and Quasar) as a full featured component driven dev stack or Histoire for a lighter solution would interest the Quasar Team.

Hoping those inputs will help you!

image

image

image

@Jizazim
Copy link

Jizazim commented Sep 20, 2023

@jclaveau where do you get import { getQuasarCtx } from '@quasar/app-vite/lib/utils/get-quasar-ctx'

I'm currently adding storybook to my project but its failing on that part:
Error: Cannot find module '@quasar/app-vite/lib/utils/get-quasar-ctx'
I was able to find that get-quasar-ctx file under @quasar/app-vite/lib/helper/get-quasar-ctx but its not a function.

@jclaveau
Copy link
Contributor

jclaveau commented Sep 22, 2023

@jclaveau where do you get import { getQuasarCtx } from '@quasar/app-vite/lib/utils/get-quasar-ctx'

I'm currently adding storybook to my project but its failing on that part: Error: Cannot find module '@quasar/app-vite/lib/utils/get-quasar-ctx' I was able to find that get-quasar-ctx file under @quasar/app-vite/lib/helper/get-quasar-ctx but its not a function.

I installed quasar as a dev dependency of my project instead of globally (using pnpm this produces no disk space issue).

Btw, just to warn you : Storybook 7 vite plugin has an issue as it calls the setup() hook and the Vue mount() function in parallel (at least a month ago it was like that). This produces some issues as the app is mounted before the Quasar boot files are fully run.

So if your components use boot files like i18n or, in my case, a boot file to set components default properties values, you will have some troubles.

I hope I will have some time to find a solution for it but for bow I'm blocked.

@mv-go
Copy link

mv-go commented Sep 29, 2023

Thank you @jclaveau for all the efforts that you've put into this. I've recently stumbled upon this issue of providing scss variables that would override default quasar values. And it's stopping me from implementing sb integration for our project's component library. I'll be definitely trying out your solution in the upcoming days, otherwise I'll have to resort to implementing our own mini-version of sb. Thanks again!

To the quasar devs that might see this issue - I think it'd be really great for the otherwise awesome and complete quasar ecosystem to have an out-of-box storybook integration.

@jclaveau
Copy link
Contributor

jclaveau commented Oct 5, 2023

I'll be definitely trying out your solution in the upcoming days, otherwise I'll have to resort to implementing our own mini-version of sb.

Maybe Histoire would be easier ton integrate https://histoire.dev. If I remember well, there is an opened issue concerning Quasar support

@Jizazim
Copy link

Jizazim commented Oct 5, 2023

@jclaveau I have been able to get it running for a few of my pages and the main two hurdles that are left have just felt impossible to resolve. Mainly if any page uses $q variable for example $q.notify will give you a error:
image
and any other $q features like $q.dark or $q.localstorage. I assume that is what you are meaning with the: 'Storybook 7 vite plugin has an issue as it calls the setup() hook and the Vue mount() function in parallel'

And the other main problem I ran into was the routing. I was able to resolve this mostly by adding:

async viteFinal(config) {
    if (config.resolve) {
      config.resolve.alias = {
        ...config.resolve.alias,
        src: path.resolve(__dirname, '../src'),
      };
    }
    return config;
  },

In my main.ts storybook file, but this only works if I have full paths on all my pages, meaning an import like:
import LoginComponent from 'components/LoginComponent.vue';
On the LoginPage.vue gives a error in storybook but I know that import works. But if I just change it to:
import LoginComponent from 'src/components/LoginComponent.vue';

Then storybook stops complaining but thats not realistic for all cases since sometimes I will have dynamic paths.

Have you maybe found a way around these problems?

@jclaveau
Copy link
Contributor

jclaveau commented Oct 6, 2023

Mainly if any page uses $q variable for example $q.notify will give you a error:
I assume that is what you are meaning with the: 'Storybook 7 vite plugin has an issue as it calls the setup() hook and the Vue mount() function in parallel'

It looks a lot like the issue due to unmounted boot files. This said, this change may have fixed in https://github.com/storybookjs/storybook/pull/23772/files#diff-761b3fb7b014b2341fea7300ea1308e11425ab0aeef90cbc5e0fa5a9acab0b5fR30

And the other main problem I ran into was the routing. I was able to resolve this mostly by adding:

async viteFinal(config) {
    if (config.resolve) {
      config.resolve.alias = {
        ...config.resolve.alias,
        src: path.resolve(__dirname, '../src'),
      };
    }
    return config;
  },

Interesting, I personally avoid using aliases.
I wonder if you speak about Vue Router but, if it's the case, to make it work I simply configured the hash mode instead of history in storybook (storybook passes other GET parameters to the view)

In my main.ts storybook file, but this only works if I have full paths on all my pages, meaning an import like: import LoginComponent from 'components/LoginComponent.vue'; On the LoginPage.vue gives a error in storybook but I know that import works. But if I just change it to: import LoginComponent from 'src/components/LoginComponent.vue';

Then storybook stops complaining but thats not realistic for all cases since sometimes I will have dynamic paths.

I personally avoid dynamic paths and aliases as it makes me loose too much time with failing intellisense.

Have you maybe found a way around these problems?
I personally staled this issue by stopping using Storybook due to tough deadlines but knowing that async setup() functions are now supported (if this PR is published) I will probably dig again.

If I were you I would begin by updating Storybook to check if Quasar boot files work now as expected

@josephdpurcell
Copy link

I got Quasar with Storybook working. Here's detail.

I've read through a few sources:

The JavaScript in Plain English (Medium) post got Quasar components to render but I was having trouble rendering my own because I ran into defineComponent not being available, which was caused by not having vite plugins added, heres my vite plugins example:

Example unplugin-auto-import/vite with storybook

CAUTION! This is a partial example, just pointing out how config.plugins.push worked for me.

import AutoImport from 'unplugin-auto-import/vite';

const config: StorybookConfig = {
  async viteFinal(config) {
    if (!config.plugins) {
      config.plugins = [];
    }
    config.plugins.push(
      AutoImport({
        imports: [
          'vue',
        ],
        dts: 'src/auto-imports.d.ts',
      }),
    );

Thank you to all who are contributing to this feature! If I were starting fresh I would look at https://github.com/tasynguyen3894/demo_quasar_storybook_i18n_pinia/ and the related blog post and then make sure relevant vite plugins are loaded.

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

No branches or pull requests

8 participants