quasar icon indicating copy to clipboard operation
quasar copied to clipboard

Provide information how to use Quasar with Storybook

Open Seanitzel opened this issue 3 years ago • 16 comments
trafficstars

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

Seanitzel avatar Dec 12 '21 18:12 Seanitzel

What is the error message you are facing?

Maybe this issue https://github.com/quasarframework/quasar/issues/11683?

freakzlike avatar Dec 28 '21 08:12 freakzlike

The error message is SassError: Undefined variable.

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

Seanitzel avatar Dec 30 '21 17:12 Seanitzel

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 avatar Feb 28 '22 22:02 jclaveau

We must use css variables as explained here https://quasar.dev/style/color-palette#dynamic-change-of-brand-colors-dynamic-theme-colors-

@badsaarow replied here giving an example https://github.com/badsaarow/quasar2-storybook-boilerplate/issues/47#issuecomment-1057931751

jclaveau avatar Mar 03 '22 13:03 jclaveau

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

arsenijesavic avatar Oct 03 '22 20:10 arsenijesavic

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.

54mu3l avatar May 19 '23 21:05 54mu3l

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/[email protected]
npm update @quasar/vite-plugin
npm update @vitejs/plugin-vue
  • [ ] Ensure Vite version by using yarn why or https://github.com/amio/npm-why

Install Storybook

  • [ ] npx storybook@latest init as idicated here https://storybook.js.org/docs/react/get-started/install
  • [ ] Storybook lint rules seems to have incompatibilities with Quasar ones so I don't recommend them. Allowing them adds plugin:storybook/recommended to .elintrc.cjs

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

jclaveau avatar Jul 03 '23 13:07 jclaveau

Btw there are some related discussions

  • https://github.com/quasarframework/quasar/discussions/13147
  • https://github.com/quasarframework/quasar/discussions/15585
  • https://github.com/quasarframework/quasar/discussions/11537
  • The feature request I just opened: https://github.com/quasarframework/quasar/discussions/16037

jclaveau avatar Jul 03 '23 17:07 jclaveau

@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.

Jizazim avatar Sep 20 '23 14:09 Jizazim

@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.

jclaveau avatar Sep 22 '23 08:09 jclaveau

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.

mv-go avatar Sep 29 '23 21:09 mv-go

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

jclaveau avatar Oct 05 '23 08:10 jclaveau

@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?

Jizazim avatar Oct 05 '23 16:10 Jizazim

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

jclaveau avatar Oct 06 '23 11:10 jclaveau

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

I've read through a few sources:

  • The comment in this thread https://github.com/quasarframework/quasar/issues/11654#issuecomment-1618295838
  • This blog post https://javascript.plainenglish.io/setup-storybook-for-the-quasar-project-vite-pinia-vue-i18n-69b41745af60 and related repo https://github.com/tasynguyen3894/demo_quasar_storybook_i18n_pinia
  • https://github.com/devhero/storybook-quasar-vite/
  • https://github.com/quasarframework/quasar/discussions/16037

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.

josephdpurcell avatar Oct 14 '23 14:10 josephdpurcell

@josephdpurcell Did you by any chance follow up with the latest versions of quasar and app-vite? Cloning your repo worked great..but trying to reproduce this with app-vite-2 has me going in circles.

Dtsiantaris avatar May 28 '24 12:05 Dtsiantaris

In case this helps anyone else trying to set up Quasar + Storybook (using TS + Vite)

https://github.com/bigjump/quasar-storybook-demo

  • First commit - I just ran npm init quasar
  • Second commit - includes the changes required to get a simple demo working

You can see the changes here:

https://github.com/bigjump/quasar-storybook-demo/compare/e8ca641ca8e1665b415d89c08d1a26c820438846..56bf27d47fca198c93dd4e25943b2557eb40c59f#diff-d42715fd3297e575fb61faba39bba1b83739d3fd533a719ccfb0d81f64862b15

In summary:

Storybook and Quasar require different versions of Vite, so we need to manage it seperately. Steps are:

  • Create .storybook folder with preview.ts and main.ts - see Storybook docs about these
  • Add storybook commands to package.json scripts
  • Add storybook dev dependencies into package.json
  • Override the Vite & ViteJs plugin in package.json
  • Add vite.config.js
  • Create your first storybook story - eg. DemoCard.stories.ts assuming you have a DemoCard.vue component
  • npm run storybook

Image

jaymcguinness avatar Feb 10 '25 11:02 jaymcguinness