Как настроить проект Quasar с помощью Storybook, Pina и Vue I18n

Недавно у меня была возможность больше поработать над проектом Quasar. Это проект «с нуля». Так что это дает мне возможность и вызов сделать что-то с Quasar. Одним из них является создание сборника рассказов для проекта «Квазар». Для тех, кто не знает о сборнике рассказов. Storybook — это мастер-класс по созданию компонентов пользовательского интерфейса. Сегодня я покажу вам руководство по настройке Storybook для проекта Quasar (включая Pinia, Vue I18n).

Чтобы вам было легче понять руководство, я буду идти шаг за шагом, а не просто показывать команду и исходный код. На каждом этапе у нас могут возникнуть некоторые проблемы, я покажу способ решения этих проблем. Пойдем.

Создайте проект «Квазар».

Чтобы настроить что-либо для проекта, первым шагом является создание проекта. На этом этапе вы можете посетить этот веб-сайт.



Или мы можем использовать эту команду для создания проекта Quasar:

yarn create quasar

В процессе создания Квазар задаст вам несколько вопросов о некоторых конфигурациях нашего проекта.

В нашем проекте я настроил как на фото выше (Typescript, Quasar 2, Vite, SCSS, Pinia, Vue-i18n, Composition API со скриптом установки).

Создайте несколько примеров компонентов

После создания проекта. Я создам несколько примеров компонентов, с которыми мы будем работать.

Чтобы наши компоненты выглядели более удобными для тестирования, я добавлю еще одно языковое сообщение для французского языка в Vue I18n. Я создам файл «src/i18n/fr/index.ts» следующим образом:

export default {
  failed: 'Action: échoué',
  success: 'Action réussie',
  yellow: 'Jaune',
  blue: 'Bleue',
  green: 'Verte'
};

Я также обновляю «src/i18n/en-US/index.ts»:

export default {
  failed: 'Action failed',
  success: 'Action was successful',
  yellow: 'Yellow',
  blue: 'Blue',
  green: 'Green'
};

Затем мы добавили новое сообщение на французском языке в I18n:

import enUS from './en-US';
import fr from './fr';

export default {
  'en-US': enUS,
  fr
};

Теперь давайте создадим компоненты нашего примера.

ColorChip

Первый компонент — это компонент, который использует I18n для отображения контента в виде чипа. Вот код этого компонента:

<template>
  <q-chip :color="color" :label="t(color)" />
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
export type Color = 'yellow' | 'green' | 'blue';

export interface ColorLabelProps {
  color: Color
}

const { t } = useI18n();

defineProps<ColorLabelProps>();
</script>

Этот компонент будет отображаться следующим образом:

ЧатСообщение

В этот компонент мы обернем Quasar QChatMessage. Нам просто нужно определить это просто так:

<template>
  <q-chat-message
    :text="[text]"
    :sent="sent"
  />
</template>
<script setup lang="ts">
withDefaults(defineProps<{
  text: string,
  sent?: boolean
}>(), {
  text: '',
  sent: false
})
</script>

ПинияБаттон

Последний компонент — это компонент, который работает с Pinia. Я буду использовать хранилище счетчиков по умолчанию в Quasar. Это код этого компонента и короткая демонстрация для него:

<template>
  <div>{{ counter }} <button @click="counterStore.increment()">{{ label }}</button></div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useCounterStore } from 'src/stores/example-store';

withDefaults(defineProps<{
  label: string
}>(), {
  label: ''
})

const counterStore = useCounterStore();
const { counter } = storeToRefs(counterStore);
</script>

Давайте установим Storybook для него.

Установить сборник рассказов

Следите за сайтом Storybook, нам просто нужно запустить эту команду:

npx storybook@latest init

Эта команда задаст нам те же вопросы, что и с Квазаром. Поэтому для нашего примера я использовал Vite вместо Webpack. Попробуем запустить команду для запуска Storybook:

yarn storybook

Мы можем получить эту ошибку:

Причиной этой ошибки является наш Vite. Из-за оболочки Quasar он собирается внутри этой библиотеки. Вот почему у нас нет Vite в пакетах. Так что Storybook не может позвонить нашему Vite. Для решения нам просто нужно установить Vite:

yarn add vite

После установки Vite мы можем запустить сборник рассказов в обычном режиме. Теперь давайте поиграем со сборником рассказов.

Решите проблемы с помощью сборника рассказов

Проблема с псевдонимом

Сначала мы начнем с ChatMessage. Я сделаю базовую историю для ChatMessage следующим образом:

import type { Meta, StoryObj } from '@storybook/vue3';

import ChatMessage from 'src/components/UI/ChatMessage.vue';

const meta = {
  title: 'UI/ChatMessage',
  component: ChatMessage,
  tags: ['autodocs'],
  argTypes: {
    text: {
      control: 'text'
    },
    sent: {
      control: 'boolean'
    }
  }
} satisfies Meta<typeof ChatMessage>;

export default meta;
type Story = StoryObj<typeof meta>;

Когда мы попытаемся открыть историю этого компонента, мы можем столкнуться с первой проблемой.

Эта проблема возникает из-за псевдонима. Quasar по умолчанию дает нам псевдоним «src» для «./src». Но сборник рассказов — нет. Вот почему мы можем столкнуться с этой ошибкой. Чтобы это исправить, нам просто нужно посетить «.storybook/main.ts», чтобы определить псевдоним в конфигурации Vite:

 import type { StorybookConfig } from '@storybook/vue3-vite';
+import path from 'path';

 const config: StorybookConfig = {
   stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
   addons: [
     '@storybook/addon-links',
     '@storybook/addon-essentials',
     '@storybook/addon-interactions',
   ],
   framework: {
     name: '@storybook/vue3-vite',
     options: {},
   },
   docs: {
     autodocs: 'tag',
   },
+  async viteFinal(config) {
+    if(config.resolve) {
+      config.resolve.alias  = {
+        ...config.resolve.alias,
+        'src': path.resolve(__dirname, "../src"),
+      };
+    }
+    return config;
   },
 };
 export default config;

В этом блоке кода нам просто нужно переопределить метод «viteFinal», чтобы определить наш псевдоним. Если мы определяем другие псевдонимы в Quasar, мы также используем тот же способ, чтобы определить их для Storybook. Проверяем результат:

Оно работает. Мы прощаемся с ошибкой псевдонима. Но это выглядит слабо, потому что вместо сообщения чата мы получаем пустой результат. Мы исправим эту проблему в следующей части.

Проблема с Квазаром.

Причиной этой проблемы является Квазар. В этом компоненте мы используем компонент, зарегистрированный в Quasar как плагин Vue. В конфигурации предварительного просмотра Storybook мы не делали этот шаг. Таким образом, Storybook не может понять эти компоненты Quasar, которые мы использовали.

Чтобы исправить это, нам просто нужно добавить плагин Quasar для экземпляра приложения Vue. Чтобы получить доступ к приложению Vue в Storybook, библиотека @storybook/vue3 предоставляет нам функцию setup, которая позволяет нам определить наш обратный вызов с аргументом app — это экземпляр приложения Vue. В этом обратном вызове мы можем установить любой плагин, который захотим. Давайте проверим этот дополнительный код:

 import type { Preview } from '@storybook/vue3';
+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;

В некоторых случаях плагин Quasar может экспортироваться по умолчанию. Таким образом, мы можем изменить импорт с import { Quasar } на import Quasar. Проверяем результат:

Мы решаем проблему, но кажется, что стиль не применяется к нашему выводу. Чтобы исправить это, нам нужно добавить в него CSS скриптом предварительного просмотра следующим образом:

+import '@quasar/extras/roboto-font/roboto-font.css';
+import '@quasar/extras/material-icons/material-icons.css';
+import '@quasar/extras/animate/fadeInUp.css';
+import '@quasar/extras/animate/fadeOutDown.css';
+import '@quasar/extras/animate/fadeInRight.css';
+import '@quasar/extras/animate/fadeOutRight.css';

+import 'quasar/dist/quasar.css';

 import type { Preview } from '@storybook/vue3';
 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;

Если вы используете другие значки, вам также необходимо добавить стиль в этот файл. Давайте проверим результат после того, как мы добавили стиль.

Тада, компонент рендерится хорошо.

Компонент Пиния

После решения проблемы, связанной с Квазаром. Мы сделаем историю для PiniaButton. Давайте проверим результат для этой истории:

Вы можете увидеть эту проблему с Pinia. Нам просто нужно решить эту проблему так же, как проблему Квазара. В функцию setup мы можем добавить плагин Pinia для экземпляра приложения Vue:

 import '@quasar/extras/roboto-font/roboto-font.css';
 import '@quasar/extras/material-icons/material-icons.css';
 import '@quasar/extras/mdi-v5/mdi-v5.css';
 import '@quasar/extras/animate/fadeInUp.css';
 import '@quasar/extras/animate/fadeOutDown.css';
 import '@quasar/extras/animate/fadeInRight.css';
 import '@quasar/extras/animate/fadeOutRight.css';
 import 'quasar/dist/quasar.css';

 import type { Preview } from '@storybook/vue3';
 import { setup } from '@storybook/vue3';
+import { createPinia } from 'pinia';
 import { Quasar } from 'quasar';
 
 setup((app) => {
   app.use(Quasar, {});
+  app.use(createPinia());
 });
 
 const preview: Preview = {
   parameters: {
     actions: { argTypesRegex: '^on[A-Z].*' },
     controls: {
       matchers: {
         color: /(background|color)$/i,
         date: /Date$/,
       },
     },
   },
 };
 
 export default preview;

Проверяем результат:

Это работает хорошо. Перейдем к следующей части.

Вью I18n

Это последняя часть основных вопросов, которые нам нужно решить. Давайте создадим историю для ColorChip и посмотрим на результат.

Я думаю, нет необходимости объяснять больше о способе решения этой проблемы. Нам просто нужно добавить плагин I18n для экземпляра приложения Vue. Но нам нужно немного изменить «src/boot/i18n.ts». Мы экспортируем плагин I18n в этот файл, чтобы другие файлы могли получить к нему доступ:

+export const i18n = createI18n({
+  locale: 'en-US',
+  legacy: false,
+  messages,
+});
 
 export default boot(({ app }) => {
   // Set i18n instance on app
-  const i18n = createI18n({
-    locale: 'en-US',
-    legacy: false,
-    messages,
-  });
   app.use(i18n);
 });

Затем добавьте плагин I18n в экземпляр Vue:

 import type { Preview } from '@storybook/vue3';
 import { setup } from '@storybook/vue3';
 import { createPinia } from 'pinia';
+import { i18n } from '../src/boot/i18n';
 import { Quasar } from 'quasar';
 
 setup((app) => {
   app.use(Quasar, {});
+  app.use(i18n);
   app.use(createPinia());
 });

Конечно, это будет хорошо работать

Но я думаю, что этого недостаточно для нас. Этот компонент использует I18n. Это означает, что у нас может быть много выходных данных для многих языков. Я думаю, нам нужен помощник, чтобы пользователи могли переключать локаль на доске Storybook. Давайте перейдем к дополнительной части для этого.

Переключатель локали

Storybook предоставляет нам несколько способов добавления наших пользовательских материалов на панель инструментов. Они называют это globalTypes . В нашем случае я создам globalTypes для раскрывающегося списка локали с помощью этого кода:

// .storybook/preview.ts
const globalTypes = {
  locale: {
    name: 'Locale',
    description: 'Internationalization locale',
    toolbar: {
      icon: 'globe',
      items: [
        { value: 'en-US', title: 'English' },
        { value: 'fr', title: 'French' },
      ],
      showName: true,
    },
  },
}

const preview: Preview = {
  globalTypes
  // our config
}

Посмотрим на результат:

Появилось выпадающее меню локали. Но никакого эффекта. Нам нужно сделать декоратор, чтобы справиться с этим. Декоратор — это функция с двумя аргументами: компонент Story, который отображает компонент Storybook, и контекст, который содержит значения контекста. В нашем случае мы получим значение переключателя локали и обновим локаль I18n. Давайте посмотрим код:

import { i18n } from '../src/boot/i18n';

const decorator = (story, context) => {
  const { locale } = context.globals;
  i18n.global.locale.value = locale;
  return story();
}

const preview: Preview = {
  decorators: [decorator]
}

Проверяем результат:

Это работа! Теперь мы можем легко переключить локаль.

Заключение

Есть то, чем я хочу поделиться в этой статье. Существует полный исходный код из «.storybook/preview.ts» и «.storybook/main.ts».

// main.ts

import type { StorybookConfig } from '@storybook/vue3-vite';
import path from 'path';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/vue3-vite',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
  async viteFinal(config) {
    if(config.resolve) {
      config.resolve.alias  = {
        ...config.resolve.alias,
        'src': path.resolve(__dirname, "../src"),
      };
    }
    return config;
  },
};
export default config;
// preview.ts

import '@quasar/extras/roboto-font/roboto-font.css';
import '@quasar/extras/mdi-v5/mdi-v5.css';
import '@quasar/extras/animate/fadeInUp.css';
import '@quasar/extras/animate/fadeOutDown.css';
import '@quasar/extras/animate/fadeInRight.css';
import '@quasar/extras/animate/fadeOutRight.css';

import 'quasar/dist/quasar.css';

import type { Preview } from '@storybook/vue3';
import { setup } from '@storybook/vue3';
import { i18n } from '../src/boot/i18n';
import { createPinia } from 'pinia';

import { Quasar } from 'quasar';

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

const globalTypes = {
  locale: {
    name: 'Locale',
    description: 'Internationalization locale',
    toolbar: {
      icon: 'globe',
      items: [
        { value: 'en-US', title: 'English' },
        { value: 'fr', title: 'French' },
      ],
      showName: true,
    },
  },
}

const decorator = (story, context) => {
  const { locale } = context.globals;
  i18n.global.locale.value = locale;
  return story();
}

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

export default preview;

Я также создаю пример репозитория Github здесь:



Я надеюсь, что эта статья будет полезна для всех вас.

Спасибо за ваше чтение.

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .