Я уже давно хотел внедрить Storybook в свои проекты Nuxt.js.

Для тех, кто не знает, Storybook - это инструмент с открытым исходным кодом для изолированной разработки компонентов пользовательского интерфейса. Ознакомьтесь с его вариантами использования.

Использование Storybook с простым Vue.js не составляет труда, но с Nuxt совсем другая история, поскольку он не работает сразу после установки. Информация там разрозненная, и мне пришлось копаться в репозиториях и примерах других людей, чтобы она работала с Nuxt, в том числе и о том, как я обычно использую Магазин.

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

Моя обычная настройка проекта включает использование Vuex Store, модуля Nuxt Axios, TailwindCSS и настраиваемого SCSS.

Это примерно то, что я хотел бы видеть при работе с Storybook без необходимости слишком сильно менять то, как я использую Nuxt в целом.

В конце этого примера у нас будет компонент List, который загружает данные извне из JSONPlaceholder.

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

Это обширное руководство, поэтому не стесняйтесь сразу переходить к нужному разделу. Вы можете взять весь этот код здесь.

Содержание

Начальная настройка

Поскольку это руководство написано с нуля, мы начинаем со свежего проекта Nuxt с использованием create-nuxt-app:

npx create-nuxt-app nuxt-storybook

Кроме того, мы обновим Nuxt до последней стабильной версии 2.5.1:

npm rm nuxt && npm i -S nuxt

Ошибка сборки?

На момент написания этой статьи обновление до Nuxt 2.5 привело к ошибке при сборке:

ERROR  Failed to compile with 1 errors                                                                                                                                          friendly-errors 13:29:07
[...]
Module parse failed: Unexpected token (7:24)                                                                                                                                     friendly-errors 13:29:07
[...]
| 
| var _0c687956 = function _0c687956() {
>   return interopDefault(import('../pages/index.vue'
|   /* webpackChunkName: "pages/index" */
|   ));

Если это все еще так, на основе этого обходного пути работает на моем компьютере ™ (macOS):

rm -rf node_modules package-lock.json
npm i -D [email protected]
npm i

При запуске npm run dev теперь должна отображаться страница приветствия Nuxt по умолчанию.

Добавление сборника рассказов

Мы установим Storybook и необходимые зависимости вручную в соответствии с их рекомендациями для Vue. Большинство зависимостей уже присутствует из-за Nuxt, и отсутствует только babel-preset-vue.

// Add Storybook & dependencies
npm i -D @storybook/vue babel-preset-vue

Теперь создайте папку с именем .storybook и добавьте в нее файл config.js.

Config.js используется в качестве «точки входа», чтобы сообщить Storybook, где искать и загружать истории, а также импортировать и использовать другие необходимые плагины или надстройки для использования с историями.

В соответствии с Рекомендациями Vue config.js изначально будет выглядеть так:

Он выполняет итерацию по каждому файлу, оканчивающемуся на .stories.js в папке stories. Потому что мне нравится, чтобы мои истории были рядом с моими компонентами, а не все сразу в одном Папка stories, я просто меняю папку на components и позволяю функции перемещаться по каждой папке в ней.

Мы вернемся к config.js позже. А пока давайте убедимся, что Storybook может загружать простую историю и отображать ее.

Добавляем нашу первую историю

Внутри каталога components создайте новую папку с именем list и внутри нее файл с именем List.vue с приведенным ниже кодом. Мы будем использовать его для создания нашего последнего компонента по ходу работы.

Ничего особенного, просто что-то для отображения нашей истории. Теперь в той же папке добавьте файл с именем List.stories.js со следующим кодом:

Теперь, чтобы запустить Storybook, нам нужно добавить сценарий запуска в package.json (чтобы запустить его на другом порту, добавьте -p <port-number>)

“storybook”: “start-storybook”

Введите innpm run storybook в свой терминал, и ваш браузер откроет новую вкладку:

Это сборник рассказов. А поскольку он использует горячую перезагрузку, вы сможете мгновенно увидеть свои изменения.

Заметили, что вторая история не работает? Это потому, что мы не сказали Storybook использовать наш компонент List для этого Story, как мы сделали для первого (открытие консоли браузера покажет вам эти ошибки).

Мы можем зарегистрировать List как глобальный компонент точно так же, как мы регистрируем их с помощью подключаемых модулей Nuxt, только в config.js, так что в итоге он будет выглядеть так:

Сейчас 2-й рассказ работает. Это было просто, чтобы дать вам представление о том, что некоторые из ваших компонентов могут использовать другие. Чтобы избежать их постоянного импорта, мы можем определить их глобально, как и мы (в оставшемся примере это не используется, поэтому вы можете удалить это).

Теперь у вас есть обычная установка Storybook, работающая с Nuxt. Но это еще не большая история.

Улучшение нашего компонента списка и добавление магазина

Прежде всего, мы добавим немного сложности в наш Список и побеспокоимся об ошибках, которые Storybook выдает нам позже.

Список должен:

  • после монтирования - получать либо фейковые пользователи, либо фейковые комментарии с помощью JSONPlaceholder;
  • перебирать каждого пользователя / комментарий и отображать его с помощью компонента ListItem;
  • использовать Vuex для отправки наших вызовов API;
  • выглядеть красивее, используя TailwindCSS и несколько пользовательских стилей;

Стили

Для стилизации мы будем использовать некоторые служебные классы TailwindCSS, а также несколько пользовательских стилей, чтобы проиллюстрировать его использование в Storybook. Я использую SCSS, поэтому нам нужно добавить обычные node-sass & sass-loader:

npm i -D node-sass sass-loader

Список примет свойство source, поэтому он знает, какой источник данных мы хотим получить. Мы также оставим его готовым к вызову соответствующего действия Store для выполнения вызовов API, как только мы их создадим.

Компонент List теперь должен выглядеть так:

Добавление магазина и вызовов API

Обычно я сохраняю свои вызовы API в действиях Магазина, поэтому я могу легко вызывать их с помощью this. $ Store.dispatch.

.env: мы будем хранить наши конечные точки в файле .env, поэтому для получения этих значений мы установим модуль @ nuxtjs / dotenv npm i -S @nuxtjs/dotenv и добавьте его в модули nuxt.config.js.

Создайте .env в корневом файле проекта и добавьте:

USERS_ENDPOINT=https://jsonplaceholder.typicode.com/users
COMMENTS_ENDPOINT=https://jsonplaceholder.typicode.com/comments

На добавление действий Store для получения пользователей и комментариев. Добавьте файл actions.js в существующий каталог store со следующим кодом:

Теперь мы можем изменить методы нашего компонента List, чтобы они вызывали эти действия при его монтировании, что в итоге будет выглядеть так:

Теперь мы получаем массив данных, возвращаемых каждой конечной точкой. Давайте их покажем.

Добавление компонента ListItem

В зависимости от того, выводим ли мы список пользователей или комментариев, мы будем отображать вариант компонента ListItem. У каждого варианта тоже будет свой компонент.

Создайте в списке папку с именем items и создайте файл с именем ListItem.vue. Вот код, который нужно добавить к нему:

Пока ничего особенного, мы изменим это через мгновение. А пока я стилизовал домашнюю страницу, чтобы мы могли видеть оба наших списка рядом:

Теперь давайте воспользуемся нашим компонентом ListItem, чтобы перебрать каждую сущность, возвращаемую нашим API, и соответствующим образом оформить ее.

Добавление компонента "Пользователь и комментарий"

Мы создадим компонент для каждой сущности на основе следующей структуры данных:

// User 
{
  "id": 1,
  "name": "Leanne Graham",
  "username": "Bret",
  "email": "[email protected]",
  "address": {
    "street": "Kulas Light",
    "suite": "Apt. 556",
    "city": "Gwenborough",
    "zipcode": "92998-3874",
    "geo": {
      "lat": "-37.3159",
      "lng": "81.1496"
    }
  },
  "phone": "1-770-736-8031 x56442",
  "website": "hildegard.org",
  "company": {
    "name": "Romaguera-Crona",
    "catchPhrase": "Multi-layered client-server neural-net",
    "bs": "harness real-time e-markets"
  }
}
// Comment
{
  "postId": 1,
  "id": 1,
  "name": "id labore ex et quam laborum",
  "email": "[email protected]",
  "body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium"
}

Добавьте файл Comment.vue в / components / list / items / с кодом:

Добавьте файл User.vue в / components / list / items / с кодом:

Примечание: для примера я добавил nuxt-ссылку. Для этого мы также добавили соответствующую страницу /pages/user/index.vue. В нем ничего нет, только для nuxt-link ссылки куда-нибудь.

Давайте изменим наш компонент ListItem, чтобы использовать эти новые компоненты:

Наконец, нам нужно изменить List.vue, чтобы мы фактически передавали ответ на вызов API как реквизиты, а не просто регистрировали его. Измените методы так, чтобы они выглядели так:

// /components/list/List.vue
[...]
methods: {
  loadUsers() {
    this.$store.dispatch('GET_USERS')
    .then(res => {
      this.entities = res.data
    })
    .catch(err => {
      console.log('API error')
      console.log(err)
    })
  },
  loadComments() {
    this.$store.dispatch('GET_COMMENTS')
    .then(res => {
      this.entities = res.data
    })
    .catch(err => {
      console.log('API error')
      console.log(err)
    })
  },
}
[...]

После некоторых незначительных изменений стиля он должен теперь выглядеть так:

Теперь мы готовы перейти к сборнику рассказов и посмотреть, что из этого получится.

Разрешение жалоб Storybook

Теперь мы устраним каждую из поднятых проблем при запуске Storybook, первая из которых:

Модуль не найден

Ошибка: не удается разрешить "@ / components / list / items / ListItem"

Если вы посмотрите примеры из Storybook, вы увидите, что он ссылается на компоненты с использованием относительных путей. Это проблема для нас, использующих Nuxt, поскольку фреймворк использует псевдоним @.

Нужно ли теперь везде использовать относительные пути? К счастью, нет. Помните, мы ранее устанавливали babel-preset-vue ? Это плюс использование псевдонима webpack позволяет нам обойти эту проблему.

Сначала создайте файл в папке .storybook с именем .babelrc со следующим:

// /.storybook/.babelrc
{
  "presets": [
    "@babel/preset-env",
    "babel-preset-vue"
  ]
}

Создайте еще один файл с именем webpack.config.js в папке .storybook со следующим:

Теперь вы можете продолжать использовать псевдоним @ для импорта компонентов.

Для краткости кода теперь мы можем изменить способ импорта компонента List в его Story с import List from './List' на import List from '@/components/list/List'.

Ошибка синтаксического анализа модуля: обработка SCSS

Сборник рассказов теперь бросает:

Ошибка синтаксического анализа модуля: неожиданный символ «#» (69:13)
Для обработки этого типа файлов может потребоваться соответствующий загрузчик.

Это потому, что мы не указали, как их загружать. Мы можем решить эту проблему, добавив модульное правило для CSS / SCSS в webpack, сделав наш файл теперь таким:

Мы также должны добавить import '@/assets/css/tailwind.css в .storybook / config.js, чтобы мы могли использовать служебные классы Tailwind.

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

Использование Vuex с Storybook

Если вы следовали рекомендациям по Vue в Storybook до этого пошагового руководства, вы уже должны импортировать и использовать Vuex в config.js.

Если нет, то вот как это должно выглядеть сейчас:

Но это никуда не годится.

Компоненты Nuxt ссылаются на Магазин как this. $ Store, и наша история не знает об этом, поэтому нам нужно создать новый магазин и передать его нашему компоненту.

Но нужно ли нам воссоздавать весь Магазин? К счастью, нет. Мы действительно создадим магазин, но повторно используем все существующие действия, геттеры, мутации или состояния, которые есть в существующем магазине.

Для этого мы создадим файл с именем store.js в папке .storybook со следующим кодом:

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

На данный момент у нас есть только история со списком пользователей, которая является источником по умолчанию. Давайте добавим еще одну историю в список комментариев и переименуем каждую:

Предупреждение для модуля @ nuxtjs / axios

Хак store.$axios.$axios = axios позволяет нам передать Axios в наш новый экземпляр магазина, но имейте в виду, что это не модуль Nuxt, поэтому у вас не будет доступа ко всем его функциям.

Чтобы использовать истории, для которых требуется Vuex, вам может потребоваться изменить способ использования $ axios, чтобы ваши истории и приложение работали, но будьте осторожны.

В случае с Торо мне нужно было изменить использование this.$axios.$get на this.$axios.get.

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

После выполнения вышеуказанных шагов мы должны теперь увидеть обе истории для нашего компонента List:

Обработка ‹nuxt-link›

Наконец-то мы можем кое-что увидеть! Но наши ссылки отсутствуют ..

Если вы откроете консоль браузера на вкладке Storybook, вы увидите, что он не знает, что такое nuxt-link (кроме того, вы всегда можете посмотреть на потенциальные ошибки там, если что-то не так. работает правильно).

Требуется окончательная настройка Storybook, чтобы они работали и функционировали.

Для этого нам нужно установить зависимость @ storybook / addon-actions: npm i -D @storybook/addon-actions и добавить их в Storybook, создав файл addons.js в .storybook каталог со строками:

// /.storybook/addons.js
import '@storybook/addon-actions'
import '@storybook/addon-actions/register'

Наконец, нам нужно import { action } from '@storybook/addon-actions в config.js и зарегистрировать измененный компонент nuxt-link на Vue. Теперь наш файл config.js должен выглядеть так:

Это заменяет все экземпляры ‹nuxt-link› на обычный элемент привязки, а также устанавливает метод журнала, показывающий путь к линту при щелчке по нему.

После этого мы больше не должны видеть никаких ошибок в консоли браузера и иметь действующие интерактивные ссылки на имена наших пользователей:

Сборник рассказов о работе с Nuxt!

Это заняло некоторое время, но нам удалось заставить Storybook хорошо работать с компонентами Vue.js в проекте Nuxt.js.

Это не полноценное руководство, поскольку нам не хватает тестов и ключевых аспектов Nuxt, таких как тег ‹no-ssr› (мне также интересно, как asyncData и Storybook может в конечном итоге работать вместе).

Бонус: разверните Storybook в Netlify

При запуске Storybook вы получаете IP-адрес, которым можете поделиться с другими в вашей локальной сети, и это здорово, если вы используете тот же Wi-Fi. Но что, если вы хотите поделиться им со своими клиентами, чтобы они могли дать вам отзыв об итерации на прошлой неделе?

В таком случае разместите его на Netlify. Просто добавьте приведенный ниже сценарий в файл package.json, который создаст статический Storybook в каталоге storybook-static:

"build-storybook": "build-storybook -c .storybook" 

Затем войдите в Netlify и выберите свой репозиторий. Определите команду сборки как npm run build-storybook, а каталог публикации как storybook-static.

После этого ваша книга историй должна обновляться каждый раз, когда вы нажимаете / объединяете в свою главную ветку. Зацени!

Доступ к переменным среды

Скорее всего, в вашем магазине или компонентах будут использоваться переменные среды.

Чтобы создать Storybook с этими переменными, вам сначала нужно предоставить их Netlify, а затем использовать DefinePlugin webpack, чтобы раскрыть их через Storybook's webpack.config.js, например:

Заключительное репо и ресурсы

Не стесняйтесь взять код на Github (https://github.com/mstrlaw/nuxt-storybook) и проверить этот материал для чтения и другие репозитории, которые были полезны для создания этого руководства. :

Присоединяйтесь и оставляйте свои мысли и предложения в комментариях ниже.

Редактирует:

  • Добавлено в способ доступа к переменным env при развертывании в Netlify;
  • Изменены фрагменты кода на Gists;