Как стать автором
Обновить
1851.47

Nuxt.js app от UI-кита до деплоя

Время на прочтение 17 мин
Количество просмотров 29K
Привет, Хабр!

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

В этой статье обсудим базу, основы создания приложения на Nuxt.js:

  • создание и конфигурация проекта,
  • assets и static: стили, шрифты, изображения, посты,
  • создание компонентов,
  • создание страниц и layouts,
  • развертывание приложения (деплой).

Смотрите, что получилось!

Немного о Nuxt.js


Nuxt — высокоуровневый фреймворк на основе Vue. Nuxt.js позволяет разрабатывать изоморфные веб-приложения из коробки, абстрагируя детали распределения серверного и клиентского кода. Благодаря такому подходу, мы экономим время и можем сосредоточиться на разработке.

Основные преимущества Nuxt:

  • SPA, SSR и пререндер уже настроены; всё, что от нас требуется, — это указать. В данном приложении используем пререндер для продуктового режима, то есть заранее генерим все страницы сайта, а дальше деплоим их на хостинг для раздачи статики.
  • Отличный SEO для всех поисковых систем — результат использования SSR или пререндера.
  • Быстрое взаимодействие с сайтом по сравнению со статическими сайтами. Это достигается благодаря подгрузке только необходимых js chunks, css styles и API запросов (большую часть этого процесса автоматизирует webpack 4, который работает под капотом Nuxt).
  • Отличные показатели Google Lighthouse / Page Speed. При правильной настройке можно получить 100/100 даже на слабом сервере.
  • CSS Modules, Babel, Postscc и другие крутые инструменты настроены заранее при использовании create-nuxt-app.
  • Заданная структура проекта позволяет комфортно работать в средних и больших командах.
  • Более 50 готовых модулей и возможность использовать любые пакеты из обширной экосистемы Vue.js.

О преимуществах Nuxt можно говорить очень долго. Это тот фреймворк, который я действительно полюбил за простоту в использовании и возможность создавать гибкие и легко масштабируемые приложения. Итак, предлагаю начать и увидеть все преимущества на деле.

Больше информации о Nuxt.js на официальном сайте. Подробные руководства также выложены здесь.

Дизайн


Продуманный, готовый дизайн, а еще лучше UI-кит, значительно ускорят и упростят разработку любого приложения. Если свободного UI-дизайнера поблизости не оказалось, ничего страшного. В рамках нашей инструкции мы справимся самостоятельно!

Специально для этой статьи я подготовил дизайн блога в современном, минималистичном стиле с простым функционалом, достаточным для демонстрации возможностей Nuxt.

Для разработки я использовал онлайн-сервис Figma. Дизайн и UI-кит доступны по ссылке. Вы можете скопировать этот шаблон и использовать его в своём проекте.

Создание проекта


Для создания проекта воспользуемся утилитой от разработчиков Nuxt create-nuxt-app, которая позволяет через cli выполнить конфигурацию шаблона приложения.

Инициализируем проект, указав его название:

npx create-nuxt-app nuxt-blog

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

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

Для этого проекта будет использована конфигурация с Typescript.

При разработке на Vue c Typescript можно использовать два API: Options API или Class API.

Функционально друг от друга они не отличаются, однако имеют разный синтаксис. Лично мне ближе синтаксис Options API, поэтому в нашем проекте будет использоваться именно он.

После создания проекта мы можем запустить наше приложение, используя команду: npm run dev. Теперь оно будет доступно на localhost:3000.

В качестве локального сервера Nuxt использует webpack-dev-server с установленным и настроенным HMR, что позволяет сделать разработку быстрой и комфортной.

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

Если ранее вы не касались этой темы, то советую обратить внимание на Jest — очень простой, но при этом мощный инструмент, который поддерживает работу с Nuxt совместно с vue-test-utils.

Структура проекта


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

-- Assets
-- Static
-- Pages
-- Middleware
-- Components
-- Layouts
-- Plugins
-- Store
-- nuxt.config.js
-- ...other files


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

Создание приложения


Перед тем, как писать код, давайте сделаем следующее:

1. Удалим стартовые компоненты и страницы, созданные Nuxt.
2. Установим pug и scss для нашего удобства и экономии времени при разработке. Выполним команду:

npm i --save-dev pug pug-plain-loader node-sass sass-loader fibers

После чего станет доступно использование атрибута lang для тегов template и style:

<template lang="pug"></template>

<style lang="scss"></style>

3. Добавим в конфигурацию stylelint поддержку глубокого селектора ::v-deep, который позволит применить стили к дочерним компонентам, игнорируя scoped. Подробнее об этом селекторе можно прочитать здесь.

{
  rules: {  
    'at-rule-no-unknown': null,  
    'selector-pseudo-element-no-unknown': [  
      true,  
      {  
        ignorePseudoElements: ['v-deep'],  
      },  
    ],  
  },
}

Все приготовления позади, переходим к следующему этапу.

Посты


Посты будут храниться в директории content/posts, которую мы создадим в корне проекта в виде набора markdown-файлов.

Давайте создадим 5 небольших файлов, чтобы далее можно было сразу начать с ними работать. Для простоты используем названия 1.md, 2.md и т. д.

В директории content создадим файл Posts.d.ts, в котором определим типы для объекта, содержащего всю необходимую информацию о посте:

export type Post = {  
  id: number  
  title: string
  desc: string
  file: string
  img: string  
}

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

Идём дальше. В этой же директории создадим ещё один файл под названием posts.ts со следующим содержимым:

import { Post } from './Post'  

export default [  
  {
    id: 1,  
    title: 'Post 1',  
    desc:  
      'A short description of the post to keep the user interested.' +  
      ' Description can be of different lengths, blocks are aligned' +  
      ' to the height of the block with the longest description',  
    file: 'content/posts/1.md',
    img: 'assets/images/1.svg',
  },  

  ...

  {  
    id: 5,  
    title: 'Post 5',  
    desc:  
      'A short description of the post to keep the user interested.' +  
      ' Description can be of different lengths, blocks are aligned' +  
      ' to the height of the block with the longest description',  
    file: 'content/posts/5.md',
    img: 'assets/images/5.svg',
  },  
] as Post[]

В свойстве img мы ссылаемся на изображения в директории assets/images, но данную директорию мы ещё не создавали, давайте сделаем это сейчас.

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

Я возьму 5 изображений с unDraw. Этот отличный ресурс постоянно обновляется и содержит множество бесплатных svg-изображений.

Теперь, когда всё готово, директория content должна иметь следующий вид:

content/
-- posts.ts
-- Posts.d.ts
-- posts/
---- 1.md
---- 2.md
---- 3.md
---- 4.md
---- 5.md


А в директории assets должна была появиться поддиректория images со следующим содержимым:

assets/
-- images/
---- 1.svg
---- 2.svg
---- 3.svg
---- 4.svg
---- 5.svg
...


Динамическое получение файлов


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

Для этого создадим в директории plugins поддиректорию mixins, а в ней файл getDynamicFile.ts со следующим содержимым:

import Vue from 'vue'  
  
export const methods = {  
  getDynamicFile(name: string) {  
    return require(`@/${name}`)
 },  
}  
  
Vue.mixin({  
  methods,  
})

Всё, что нам остаётся, — это подключить данный миксин в файле nuxt.config.js:

{
  plugins: [  
    '~plugins/mixins/getDynamicFile.ts',  
  ],
}

Шрифты


После этапа создания постов подключим шрифты. Самый простой вариант это сделать — замечательная библиотека Webfontloader, которая позволяет получить любой шрифт с Google Fonts. Однако в коммерческой разработке чаще используют собственные шрифты, поэтому давайте разберём здесь именно такой случай.

В качестве шрифта для нашего приложения был выбран Rubik, который распространяется под лицензией Open Font License. Скачать его можно всё с того же Google Fonts.

Обратите внимание, что в скачанном архиве шрифты будут в формате otf, но, так как мы работаем с web, то лучшим выбором для нас будут форматы woff и woff2.Они имеют меньший размер, чем любые другие форматы, но при этом отлично поддерживаются во всех современных браузерах. Для конвертации otf в нужные нам форматы можно воспользоваться одним из множества бесплатных онлайн-сервисов.

Итак, у нас есть шрифты в нужных нам форматах, пришло время подключать их в проект. Для этого создадим в директории static поддиректорию fonts и добавим туда наши шрифты. Создадим в этой же директории файл fonts.css, который будет отвечать за подключение наших шрифтов в приложении со следующим содержимым:

@font-face {  
  font-family: "Rubik-Regular";  
  font-weight: normal;  
  font-style: normal;  
  font-display: swap;  
  src:  
	  local("Rubik"),  
	  local("Rubik-Regular"),  
	  local("Rubik Regular"),  
	  url("/fonts/Rubik-Regular.woff2") format("woff2"),  
	  url("/fonts/Rubik-Regular.woff") format("woff");  
}  
  
...

Полное содержимое файла можно увидеть в репозитории.

Стоит обратить внимание на 2 вещи:

1. Мы указываем font-display: swap;, определяя, как шрифт, подключенный через font-face, будет отображаться в зависимости от того, загрузился ли он и готов ли к использованию.
В данном случае мы не задаём период блокировки и задаем бесконечный период подмены. То есть загрузка шрифта происходит в фоне и не блокирует загрузку страницы, а шрифт отобразится по готовности.

2. В src мы указываем порядок загрузки по приоритетности. Сначала мы проверяем, установлен ли нужный шрифт у пользователя на устройстве, проверяя возможные варианты названия шрифта. Если не находим его, то проверяем, поддерживает ли браузер более современный формат woff2, и, если нет, то переходим к следующему формату woff. Есть вероятность, что пользователь использует устаревший браузер (например, IE < 9), в этом случае в дальнейшем мы укажем в качестве fallback встроенные в браузер шрифты.

После создания файла с правилами загрузки шрифтов нужно подключить его в приложении — в файле nuxt.config.js в секции head:

{
  head: {  
    link: [  
      {  
        as: 'style',  
        rel: 'stylesheet preload prefetch',  
        href: '/fonts/fonts.css',  
      },  
    ],  
  },
}

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

Сразу добавим в директорию static favicon нашего приложения, который можно сгенерировать, воспользовавшись любым бесплатным онлайн-сервисом.

Теперь директория static выглядит так:

static/
-- fonts/
---- fonts.css
---- Rubik-Bold.woff2
---- Rubik-Bold.woff
---- Rubik-Medium.woff2
---- Rubik-Medium.woff
---- Rubik-Regular.woff2
---- Rubik-Regular.woff
-- favicon.ico


Переходим к следующему этапу.

Переиспользуемые стили


В нашем проекте все используемые стили описаны единым набором правил, что существенно облегчает разработку, поэтому давайте перенесем эти стили из Figma в файлы проекта.
В директории assets создадим поддиректорию styles, в которой будем хранить все переиспользуемые стили в проекте. В свою очередь, директория styles будет содержать файл variables.scss со всеми нашими scss-переменными.

Cодержимое файла можно увидеть в репозитории.

Теперь мы должны подключить эти переменные в проект таким образом, чтобы они были доступны в любом нашем компоненте. В Nuxt для этой цели используется модуль @nuxtjs/style-resources.

Установим этот модуль:

npm i @nuxtjs/style-resources

И добавим в nuxt.config.js следующие строки:

{
  modules: [
    '@nuxtjs/style-resources',
  ],

  styleResources: {  
    scss: ['./assets/styles/variables.scss'],  
  },
}

Отлично! В любом компоненте будут доступны переменные из этого файла.

Следующий шаг — создание нескольких классов-помощников и глобальных стилей, которые будут использоваться во всём приложении. Этот подход позволит централизованно управлять общими стилями и быстро адаптировать приложение, если внешний вид макетов будет изменён дизайнером.

Создадим в директории assets/styles поддиректорию global со следующими файлами:

1. typography.scss — файл будет содержать все классы-помощники для текста, включая ссылки.
Обратите внимание, что эти классы-помощники меняют стили в зависимости от разрешения устройства пользователя: смартфона или ПК.

2. transitions.scss — файл будет содержать глобальные стили анимаций, как для переходов между страницами, так и для анимаций внутри компонентов, если в будущем нам это понадобится.

3. other.scss — файл будет содержать глобальные стили, которые пока не выделить в какую-то отдельную группу.

Класс .page будет использоваться как общий контейнер для всех компонентов на странице и будет формировать правильные отступы на странице.

Класс .section будет использован для обозначения границ логических блоков, а класс .content — для ограничения ширины контента и его центрирования на странице.

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

4. index.scss — общий файл, который будет использован как единая точка экспорта всех глобальных стилей.

Полное содержимое файлов можно увидеть на Github.

На данном этапе подключим эти глобальные стили, чтобы они стали доступны во всём приложении. Для этой задачи Nuxt предоставляет нам секцию css в файле nuxt.config.js:

{
  css: ['~assets/styles/global'],
}

Стоит сказать, что в дальнейшем при присваивании css-классов будет использована следующая логика:

1. Если у тега есть как классы-хелперы, так и локальные классы, то локальные классы будут напрямую добавлены к тегу, например, p.some-local-class, а классы-хелперы указаны в свойстве class, например, class=«body3 medium».

2. Если у тега есть только классы-хелперы или только локальные классы, то они будут напрямую добавлены к тегу.

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

Перед разработкой давайте установим и подключим reset.css, чтобы во всех браузерах наша вёрстка выглядела одинаково. Для этого установим нужный пакет:

npm i reset-css

И подключим его в файле nuxt.config.js в уже знакомой нам секции css, которая теперь будет выглядеть так:

{
  css: [
    '~assets/styles/global',
    'reset-css/reset.css',
  ],
}

Получилось? Если да, мы готовы переходить к следующему этапу!

Layouts


В Nuxt Layouts являются обёртками над страницами, которые позволяют переиспользовать между ними общие компоненты и реализовывать необходимую общую логику. Так как наше приложение предельно простое, то нам будет достаточно использовать layout по умолчанию — default.vue.

Кроме того, в Nuxt для страницы ошибок, например 404, используется отдельный layout, который фактически является простой страницей.

Layouts в репозитории.

default.vue

Наш default.vue не будет иметь никакой логики и будет выглядеть следующим образом:

<template lang="pug">  
div  
  nuxt
  db-footer
</template>

Здесь мы используем 2 компонента:

1. nuxt — при сборке будет заменён на конкретную страницу, которую запросил пользователь.

2. db-footer — это наш собственный компонент Footer (мы напишем его чуть позже), который будет автоматически добавлен на каждую страницу нашего приложения.

error.vue

По умолчанию при любой ошибке, возвращенной с сервера в статусе http, Nuxt делает редирект на layout/error.vue и передаёт через входной параметр с названием error объект, который содержит описание полученной ошибки.

Давайте посмотрим, как выглядит секция script, которая поможет унифицировать работу с полученными ошибками:

<script lang="ts">  
import Vue from 'vue'  
  
type Error = {  
  statusCode: number  
  message: string  
}  
  
type ErrorText = {  
  title: string  
  subtitle: string  
}  
  
type ErrorTexts = {  
  [key: number]: ErrorText  
  default: ErrorText  
}  

export default Vue.extend({  
  name: 'ErrorPage',  
  
  props: {  
    error: {  
      type: Object as () => Error,  
      required: true,  
    },  
  },  
  
  data: () => ({  
    texts: {  
      404: {  
        title: '404. Page not found',  
        subtitle: 'Something went wrong, no such address exists',  
      },  
      default: {  
        title: 'Unknown error',  
        subtitle: 'Something went wrong, but we`ll try to figure out what`s wrong',  
      },  
    } as ErrorTexts,  
  }),  

  computed: {  
    errorText(): ErrorText {  
      const { statusCode } = this.error  
      return this.texts[statusCode] || this.texts.default  
    },  
  },  
})  
</script>

Что здесь происходит:

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

2. В объекте data мы создаём словарь, который будет содержать сообщения для всех ошибок, для которых мы хотим отображать уникальное сообщение и сообщение по умолчанию для всех остальных.

3. В вычисляемом свойстве errorText мы проверяем, есть ли в словаре полученная ошибка. Если ошибка есть, то возвращаем для неё сообщение. Если ошибки нет, возвращаем сообщение по умолчанию.

В этом случае наш шаблон будет выглядеть так:

<template lang="pug">  
section.section  
  .content  
    .ep__container  
      section-header(  
        :title="errorText.title"  
        :subtitle="errorText.subtitle"  
      )  

      nuxt-link.ep__link(  
        class="primary"  
        to="/"  
      ) Home page  
</template>

Обратим внимание, что здесь мы используем глобальные служебные классы .section и .content, которые создали ранее в файле assets/styles/global/other.scss. Они позволяют отображать контент по центру страницы.

Здесь используется компонент section-header, который ещё не создан, но в дальнейшем он будет являться универсальным компонентом для отображения заголовков. Мы реализуем его, когда будем говорить о компонентах.

Директория layouts выглядит так:

layouts/
-- default.vue
-- error.vue


Приступим к созданию компонентов.

Компоненты


Компоненты — это кирпичики, из которых будет состоять наше приложение. Давайте начнём с тех компонентов, которые уже встречались выше.

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

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

Взглянем на секцию script этого компонента:

<script lang="ts">  
import Vue from 'vue'  

export default Vue.extend({  
  name: 'SectionHeader',  

  props: {  
    title: {  
      type: String,  
      required: true,  
    },  
    subtitle: {  
      type: String,  
      default: '',  
    },  
  },  
})  
</script>

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

<template lang="pug">  
section.section  
  .content  
    h1.sh__title(  
      class="h1"  
    ) {{ title }}  

    p.sh__subtitle(  
      v-if="subtitle"  
      class="body2 regular"  
    ) {{ subtitle }}  
</template>

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

LinkToHome

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

Этот компонент совсем крошечный, поэтому приведу весь его код сразу (без стилей):

<template lang="pug">  
section.section  
  .content  
    nuxt-link.lth__link(  
      to="/"  
      class="primary"  
    )  
      img.lth__link-icon(  
        src="~/assets/icons/home.svg"  
        alt="icon-home"  
      )  
      | Home  
</template>  
  
<script lang="ts">  
import Vue from 'vue'  

export default Vue.extend({  
  name: 'LinkToHome',  
})  
</script> 

Обратите внимание, что мы запрашиваем иконку home.svg из директории assets/icons. Предварительно нужно создать данную директорию и добавить туда нужную иконку.

DbFooter

Компонент DbFooter очень прост. Он содержит copyright и ссылку для создания письма.
Требования понятны, давайте начнём реализацию с секции script:

<script lang="ts">  
import Vue from 'vue'  

export default Vue.extend({  
  name: 'DbFooter',  

  computed: {  
    copyright(): string {
      const year = new Date().getUTCFullYear()
      return ` ${year} · All rights reserved`
    },  
  },  
})  
</script>

В DbFooter у нас есть всего одно вычисляемое свойство, которое возвращает текущий год, сконкатенированный с заданной строкой. Теперь взглянем на шаблон:

<template lang="pug">  
section.section  
  .content  
    .footer  
      a.secondary(
        href="mailto:example@mail.com?subject=Nuxt blog"
      ) Contact us  
      p.footer__copyright(
        class="body3 regular"
      ) {{ copyright }}  
</template>

При клике на ссылку Contact us мы будем открывать нативный почтовый клиент и сразу устанавливать тему сообщения. Это решение подойдёт для демо нашего приложения, но в реальной жизни более подходящим решением будет реализовать форму обратной связи для отправки сообщений напрямую с сайта.

PostCard

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

<script lang="ts">  
import Vue from 'vue'  
import { Post } from '~/content/Post'  

export default Vue.extend({  
  name: 'PostCard',  

  props: {  
    post: {  
      type: Object as () => Post,  
      required: true,  
    },  
  },  

  computed: {  
    pageUrl(): string {  
      return `/post/${this.post.id}`  
    },  
  },  
})  
</script>

В секции script мы определяем один входной параметр post, который будет содержать всю необходимую информацию о посте.

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

Шаблон будет выглядеть следующим образом:

<template lang="pug">  
nuxt-link.pc(:to="pageUrl")  
  img.pc__img(  
    :src="getDynamicFile(post.img)"  
    :alt="`post-image-${post.id}`"  
  )  

  p.pc__title(class="body1 medium") {{ post.title }}  
  p.pc__subtitle(class="body3 regular") {{ post.desc }}  
</template>

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

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

PostList

Основной компонент на главной странице будет состоять из счётчика постов сверху и списка самих постов.

Секция script для этого компонента:

<script lang="ts">  
import Vue from 'vue'  
import posts from '~/content/posts'  

export default Vue.extend({  
  name: 'PostList',  
  
  data: () => ({  
    posts,  
  }),  
})  
</script>

Отметим, после импорта массива с постами мы добавляем их в объект data, чтобы в дальнейшем у шаблона был доступ к этим данным.

Сам шаблон выглядит так:

<template lang="pug">  
section.section  
  .content  
    p.pl__count(class="body2 regular")  
      img.pl__count-icon(  
        src="~/assets/icons/list.svg"  
        alt="icon-list"  
      )  
      | Total {{ posts.length }} posts  

    .pl__items  
      post-card(  
        v-for="post in posts"  
        :key="post.id"  
        :post="post"  
      )  
</template>

Чтобы всё корректно работало, не забудьте добавить иконку list.svg в директорию assets/icons.

PostFull

PostFull — это основной компонент на отдельной странице поста, который будет отвечать за отображение текста поста.

Для этого компонента нам потребуется модуль @nuxtjs/markdownit, который будет отвечать за преобразование md в html.

Установим его:

npm i @nuxtjs/markdownit

После этого добавим @nuxtjs/markdownit в секцию modules файла nuxt.config.js:

{
  modules:  [
    '@nuxtjs/markdownit',
  ],
}

Отлично! Начнем реализацию компонента. Как всегда, с секции script:

<script lang="ts">  
import Vue from 'vue'  
import { Post } from '~/content/Post'  
  
export default Vue.extend({  
  name: 'PostFull',  
  
  props: {  
    post: {  
      type: Object as () => Post,  
      required: true,  
    },  
  },  
})  
</script>

В секции script мы определяем один входной параметр post, который будет содержать всю необходимую информацию о посте.

Переходим к шаблону:

<template lang="pug">  
section.section  
  .content  
    img.pf__image(  
      :src="getDynamicFile(post.img)"  
      :alt="`post-image-${post.id}`"  
    )  

    .pf__md(v-html="getDynamicFile(post.file).default")  
</template>

Как можно заметить, мы динамически получаем и рендерим с помощью нашего миксина getDynamicFile как изображение, так и .md файл.

Я думаю, вы обратили внимание, что для рендеринга файла мы используем стандартный атрибут v-html, так как всю остальную работу за нас сделает @nuxtjs/markdownit. Невероятно просто!

Для доступа к кастомизации стилей нашего отрендеренного .md файла мы можем использовать селектор ::v-deep. Посмотрите на Github, как реализовано для этого компонента.

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

Страницы


Когда все компоненты готовы, мы можем приступить к созданию страниц.

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

Структура директории pages:

pages/
-- index.vue
-- post/
---- _id.vue


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

<template lang="pug">  
.page  
  section-header(  
    title="Nuxt blog"  
    subtitle="The best blog you can find on the global internet"  
  )  

  post-list  
</template>  
  
<script lang="ts">  
import Vue from 'vue'  

export default Vue.extend({  
  name: 'HomePage',  
})  
</script>

Для установки правильных отступов мы использовали глобальный класс .page, который создали ранее в assets/styles/global/other.scss.

Отдельная страница поста будет выглядеть немного сложнее. Давайте посмотрим сначала на секцию script:

<script lang="ts">  
import Vue from 'vue'  
import { Post } from '~/content/Post'  
import posts from '~/content/posts'

export default Vue.extend({  
  validate({ params }) {  
    return /^\d+$/.test(params.id)  
  },  
  
  computed: {  
    currentId(): number {  
      return Number(this.$route.params.id)  
    },  
    currentPost(): Post | undefined {  
      return posts.find(({ id }) => id === this.currentId)  
    },  
  },  
})  
</script>

Мы видим метод validate. Этот метод отсутствует во Vue, его предоставляет нам Nuxt для валидации полученных от роутера параметров. Validate будет вызываться каждый раз при переходе к новому маршруту. В данном случае мы просто проверяем, что переданный нам id является числом. Если валидация не прошла, то пользователю будет возвращена страница ошибки error.vue.

Здесь реализованы 2 вычисляемых свойства. Рассмотрим подробнее, что они делают:

1. currentId — это свойство возвращает нам текущий id поста (который был получен из параметров роутера), предварительно преобразовав его в number.

2. currentPost возвращает объект с информацией о выбранном посте из общего массива всех постов.

Кажется, с этим разобрались. Давайте взглянем на шаблон:

<template lang="pug">  
.page
  link-to-home  

  section-header(  
    :title="currentPost.title"  
  )  

  post-full(  
    :post="currentPost"  
  )
</template>

Секция стилей для этой страницы так же, как и для главной страницы, отсутствует.
Код страниц на Github.

Деплой на Hostman


Ура! Наше приложение почти готово. Пришло время заняться его деплоем.

Для этой задачи я буду использовать облачную платформу Hostman, которая позволяет автоматизировать процесс деплоя.

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

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

Сразу после этого автоматически запустится публикация и будет создан бесплатный домен в зоне *.hostman.site с установленным ssl сертификатом от Let's Encrypt.

С этого момента при каждом новом пуше в выбранную ветку (master по умолчанию) будет выполняться деплой новой версии приложения. Невероятно просто и удобно!

Заключение


Итак, что мы имеем:


Мы попробовали показать на практике, как работать с фреймворком Nuxt.js. Нам удалось создать простое приложение от начала и до конца, от создания UI-кита до организации деплоя.

Если вы выполнили все шаги, описанные в этой статье, вас можно поздравить с созданием своего первого приложения на Nuxt.js. Было сложно? Как вам работа с этим фреймворком? Если есть вопросы или пожелания, не стесняйтесь писать в комментариях.
Теги:
Хабы:
+4
Комментарии 4
Комментарии Комментарии 4

Публикации

Информация

Сайт
timeweb.cloud
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Timeweb Cloud