Статические веб-сайты в настоящее время снова становятся популярными. Информационным сайтам и сайтам с брошюрами больше не нужно использовать системы управления контентом, такие как WordPress, для динамического обновления.
С помощью генераторов статических сайтов вы можете получать свой контент из динамических источников, таких как Headless CMS, а также из файлов, таких как файлы Markdown.
Gridsome - отличный генератор статических сайтов на основе Vue.js, который легко использовать для создания статических сайтов. В нем есть плагины для получения контента из различных источников, таких как файлы WordPress и Markdown. Вы можете добавлять плагины для расширения функциональности вашего веб-сайта, такие как синтаксический анализ Markdown и манипуляции с изображениями. Gridsome создает статические веб-сайты из этих источников, передавая контент в шаблоны для их отображения. Данные извлекаются с помощью запросов GraphQL. Вы также можете создавать страницы, чтобы помещать свой контент в список, а также получать метаданные для каждого сообщения, например теги. Каждый шаблон, страница или макет может иметь один статический запрос для получения данных веб-сайта и один запрос страницы для получения данных для содержания.
В этой статье мы создадим блог с Gridsome и будем использовать Markdown для контента. Вы должны знать Vue.js, прежде чем сможете создавать веб-сайт с помощью Gridsome.
Мы начинаем создание веб-сайта с помощью Gridsome CLI. Запустить:
npm install --global @gridsome/cli
Которая устанавливает программу CLI.
Затем создайте проект Gridsome, запустив:
gridsome create gridsome-how-to-blog
Затем заходим в созданную папку проекта и устанавливаем несколько пакетов. Нам нужны плагины Gridsome Source Filesystem и Gridsome Transformer Remark. Чтобы установить их, запустите:
npm i @gridsome/source-filesystem @gridsome/transformer-remark
Затем запускаем:
gridsome develop
Это запускает проект.
Создав проект и установив плагины, мы можем приступить к написанию кода и созданию контента. Сначала мы создаем сообщения для нашего блога. Создайте content
папку, если она еще не существует, а затем внутри нее создайте папку posts
. Затем мы создаем следующие файлы:
check-variable-number.md
:
--- path: "/blog/check-variable-number" date: "2019-05-04" title: "How to Check if a Variable is a Number" tags: ["number"] --- We can check if a variable is a number in multiple ways. ## isNaN We can check by calling `isNaN` with the variable as the argument. It also detects if a string’s content is a number. For example: ``` isNaN(1) // false isNaN('1') // false isNaN('abc') // true ``` **Note:** `isNaN(null)` is `true` . ### typeof Operator We can use the `typeof` operator before a variable to check if it’s a number, like so: ``` typeof 1 == 'number' // true typeof '1' == 'number' // false ``` 
clone-array.md
:
--- path: "/blog/clone-array" date: "2019-05-04" title: "How to Clone Array in JavaScript" tags: ["array"] --- There are a few ways to clone an array in JavaScript, ### Object.assign `Object.assign` allows us to make a shallow copy of any kind of object including arrays. For example: <pre>const a = [1,2,3]; const b = Object.assign([], a); // [1,2,3]</pre> ### Array.slice The `Array.slice` function returns a copy of the original array. For example: <pre>const a = [1,2,3]; const b = a.slice(0); // [1,2,3]</pre> ### Array.from The `Array.slice` function returns a copy of the original array. It takes array like objects like `Set` and it also takes an array as an argument. <pre>const a = [1,2,3]; const b = Array.from(a); // [1,2,3]</pre> ### Spread Operator The fastest way to copy an array, which is available with ES6 or later, is the spread operator. <pre>const a = [1,2,3]; const b = [...a]; // [1,2,3]</pre> ### JSON.parse and JSON.stringify This allows for deep copy of an array and only works if the objects in the array are plain objects. It can be used like this: <pre>const a = [1,2,3]; const b = JSON.parse(JSON.stringify(a)); // [1,2,3]</pre>
concat-array.md
:
--- path: "/blog/concat-array" date: "2019-05-04" title: "How to Concatenate Array in JavaScript" tags: ["array"] --- There are a few ways to concatenate arrays in JavaScript. ## Array.concat We can all `Array.concat` on an array to combine 2 arrays and return the new one. For example: ``` const a = [1,2,3]; const b = [4,5]; const c = a.concat(b) // [1,2,3,4,5] ``` ## Array.push We can push elements of one array into another. ``` const a = [1,2,3]; const b = [4,5]; let c = Object.assign([], a); for (let i = 0; i < b.length; i++){ c.push(b[i]); } console.log(c); // [1,2,3,4,5] ``` What we did is make a copy of `a` and assigned it to `c` , then pushed the elements of `b` by looping through it and adding them to the end of `c` . ## Spread Operator With ES6 or later, we can use the spread operator to spread the items from another array into a new array by doing the following: ``` const a = [1,2,3]; const b = [4,5]; const c = [...a, ...b]; console.log(c); // [1,2,3,4,5] ``` 
exponentation.md
:
--- path: "/blog/exponentiation" date: "2019-05-04" title: "How to do Exponentiation in JavaScript" tags: ["number"] --- There are multiple to compute exponents with JavaScript. The newest way is the exponentiation operator `**`, available with ES2016 or higher. For example, we can do this: ``` const a = 2 ** 3; // 8 ``` It is right associative, so `a ** b ** c` is equal to `a ** (b ** c)`. This works with all exponents. For example: ``` const a = 2 ** (3 ** 4); const b = 2 ** 3 ** 4; a == b // true, both are 2.4178516392292583e+24 ``` Detail browser compatibility is available at [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Browser_compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Browser_compatibility) We can also use the `Math.pow` function, like this: ``` const a = Math.pow(2,3) // 8 ``` It takes 2 arguments, the first is the base and the second is the exponent. `Math.pow` works with all exponents. `Math.pow` is compatible with all recent browsers. 
get-length-obj.md
:
--- path: "/blog/get-length-obj" date: "2019-05-04" title: "How to Get the Length of An Object" tags: ["object"] --- There are 2 ways to get the length of the list of keys of an object. ## Object.keys `Object.keys` gets the top level list of keys of an object and returns an array of them. For example: ``` const a = {foo: 1, bar: 2}; const length = Object.keys(a).length // 2 ``` ## Object.getPropertyNames `Object.getPropertyNames` also gets a list of all top level of keys of an object and return them as an array. For example: ``` const a = {foo: 1, bar: 2}; const length = Object.`getOwnPropertyNames`(a).length // 2 ``` ## for…in Loop There is a special loop for looping through the keys of an object. You can do the following: ``` const a = {foo: 1, bar: 2}; let keysCount = 0; for (let key in a) { keysCount++; } console.log(`keysCount) // 2 ``` 
repeat-strings.md
:
--- path: "/blog/repeat-strings" date: "2019-05-04" title: "How to Repeat Strings with JavaScript" tags: ["string"] --- There are a few ways to repeat a string in JavaScript. JavaScript strings have a built in `repeat()` function. You can also use a loop to do the same thing. ## String.repeat Function To use the `repeat` function, you pass in the number of times you want to repeat the string as an argument. It returns a new string For example: ``` const hello = "hello"; const hello5 = A.repeat(5); console.log(hello5); // "hellohellohellohellohello" ``` ## Use a loop You can use `for` loop and `while` loop to do repeatedly concatenate strings. Using a `for` loop, you can do: ``` const hello = "hello"; ``` With a `while` loop, you can do: ``` const hello = "hello"; ``` They both involve increment indexes up to the maximum. 
send-email.md
:
--- path: "/blog/send-email" date: "2019-05-04" title: "How to Send Email with SendGrid in Node.js Apps" tags: ["array"] --- SendGrid is a great service made by Twilio for sending emails. Rather than setting up your own email server for sending email with your apps, we use SendGrid to do the hard work for us. It also decrease the chance of email ending up in spam since it is a known trustworthy service. It also has very easy to use libraries for various platforms for sending emails. Node.js is one of the platforms that are supported. To send emails with SendGrid, install the SendGrid SDK package by running `npm i @sendgrid/mail` . Then in your code, add `const sgMail = require(‘@sendgrid/mail’);` to import the installed package. Then in your code, you send email by: ``` sgMail.setApiKey(process.env.SENDGRID_API_KEY); const msg = { to: email, from: '[email protected]', subject: 'Example Email', text: ` Dear user, Here is your email.`, html: ` <p>Dear user,</p></pre> Here is your email.</p>`, }; sgMail.send(msg); ``` where `process.env.SENDGRID_API_KEY` is the SendGrid’s API, which should be stored as an environment variable since it is a secret. Testing is easy since you don’t need to set up a local development email server. Sending email is this simple and easy with SendGrid API. It is also free if you send small amounts of email, which is a great benefit. 
После этого мы можем создать код для отображения контента на нашем веб-сайте. В папке layouts
замените существующий код Default.vue
на:
<template> <div> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="#">{{ $static.metadata.siteName }}</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" > <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <g-link class="nav-link" to="/">Home</g-link> </li> <li class="nav-item dropdown" v-if="$page.allTag"> <g-link class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" >Tags</g-link> <div class="dropdown-menu" aria-labelledby="navbarDropdown"> <g-link class="nav-link" :to="tag.node.path" v-for="(tag, index) in $page.allTag.edges" :key="index" >{{tag.node.id}}</g-link> </div> </li> </ul> </div> </nav> <div class="layout"> <slot /> </div> </div> </template> <static-query> query { metadata { siteName } } </static-query> <style> nav.navbar { background-color: lightgreen !important; } .layout { max-width: 760px; margin: 0 auto; padding-left: 20px; padding-right: 20px; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; height: 80px; } .nav__link { margin-left: 20px; } </style>
Это компонент макета, которым мы оборачиваем наш контент. Там мы добавляем компонент панели навигации Bootstrap в верхнюю часть нашего компонента макета, а в нижнюю часть компонента у нас есть компонент slot
для отображения нашего контента через шаблоны, которые мы создадим.
Мы получаем список тегов из раздела page-query
наших шаблонов и страниц, и будет отображаться раскрывающееся меню со списком тегов, если они существуют. У каждого тега будет ссылка.
В разделе static-query
компонента содержатся метаданные веб-сайта. Здесь мы получаем siteName
, чтобы показать название веб-сайта. Результат static-query
сохраняется в переменной this.$static
в компоненте, который имеет раздел static-query
, поэтому мы можем использовать $static
для отображения результата в нашем шаблоне, как мы делали выше.
Затем в Index.vue
в папке pages
замените существующий код на:
<template> <Layout> <div class="post-list"> <div v-for="(edge, index) in $page.allPost.edges" :key="index"> <h1 class="title" v-html="edge.node.title" /> <p class="date" v-html="edge.node.date" /> <p class="description" v-html="edge.node.description" /> <p>{{edge.node.timeToRead}} min read</p> <g-link :to="edge.node.path" class="read">Read More...</g-link> <hr class="line" /> </div> </div> </Layout> </template> <script> export default { metaInfo: { title: "Gridsome How to Blog" } }; </script> <style> .home-links a { margin-right: 1rem; } </style> <page-query> query { allPost { totalCount edges { node { id title timeToRead date (format: "D MMMM YYYY") path } } }, allTag { edges { node { id path } } } } </page-query>
Это домашняя страница нашего сайта. Показываем список постов, которые мы создали в папке content/posts
ранее. Чтобы получить данные публикации, мы добавили page-query
в компонент выше. Результаты устанавливаются в this.$page
для page-query
запросов, поэтому мы можем использовать $page
для отображения данных в шаблоне, как мы это делали выше.
У нас есть запрос allPost
для получения всех сообщений и запрос allTag
для получения тегов для компонента Layout
, который записан в layouts/Default.vue
, который мы изменили выше.
Затем мы создаем наши шаблоны для отображения написанных нами сообщений. В папке templates
создаем Post.vue
и добавляем:
<template> <Layout> <br /> <g-link to="/" class="link">← Go Back</g-link> <div class="post-title"> <h1>{{$page.post.title}}</h1> <p class="post-date">{{ $page.post.date}} | {{$page.post.timeToRead}} min read</p> </div> <div class="post-content"> <p v-html="$page.post.content" /> </div> </Layout> </template> <page-query> query Post ($path: String!) { post: post (path: $path) { id title content date (format: "D MMMM YYYY") timeToRead } } </page-query>
В этом компоненте мы получаем данные для отдельного сообщения по его пути, а результаты присваиваются переменной this.$page
, поскольку это page-query
, как и раньше.
Затем создайте Tag.vue
в папке templates
и добавьте:
<template> <Layout> <h1>Articles Tagged with: {{ $page.tag.title }}</h1> <ul> <li v-for="edge in $page.tag.belongsTo.edges" :key="edge.node.id"> <g-link :to="edge.node.path">{{ edge.node.title }}</g-link> </li> </ul> </Layout> </template> <page-query> query Tag($id: ID!) { tag(id: $id) { title belongsTo { edges { node { ... on Post { id title path } } } } }, allTag { edges { node { id path } } } } </page-query>
чтобы получить все теги с запросом allTag
и все сообщения с тегом, который мы написали в файлах Markdown с запросом tag
. Запрос allTag
используется в компоненте Layout
, а запрос tag
используется в этом компоненте. Мы просматриваем результаты tag
запроса в этом файле.
Затем в gridsome.config.js
замените существующий код на:
// This is where project configuration and plugin options are located. // Learn more: https://gridsome.org/docs/config // Changes here require a server restart. // To restart press CTRL + C in terminal and run `gridsome develop` module.exports = { siteName: "Gridsome How to Blog", siteDescription: "A how to blog designed with Gridsome", plugins: [ { use: "@gridsome/source-filesystem", options: { path: "content/posts/**/*.md", typeName: "Post", route: "/blog/:slug", refs: { tags: { typeName: "Tag", route: "/tag/:id", create: true } } } } ] };
добавить плагин @ gridsome / source-filesystem, чтобы мы могли получать файлы Markdown и отображать их на нашем сайте. path
в объекте options
- это путь к созданным нами файлам Markdown. route
- относительный URL-адрес сообщений. Включен объект tags
, поэтому мы создаем страницы для перечисления сообщений с данным тегом. typename
совпадает с именем файла наших шаблонов. Итак, typename: "Post"
означает, что мы используем templates/Post.vue
для отображения содержимого нашего сообщения, а typename: "Tag"
будет использовать templates/Tag.vue
для отображения сообщений, помеченных данным тегом.
Затем в mains.js
замените существующий код на:
// This is the main.js file. Import global CSS and scripts here. // The Client API can be used here. Learn more: gridsome.org/docs/client-api import DefaultLayout from "~/layouts/Default.vue"; export default function(Vue, { router, head, isClient }) { // Set default layout as a global component Vue.component("Layout", DefaultLayout); head.link.push({ rel: "stylesheet", href: "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css", integrity: "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T", crossorigin: "anonymous" }); // Add an external Javascript before the closing </body> tag head.script.push({ src: "https://code.jquery.com/jquery-3.3.1.slim.min.js", integrity: "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo", crossorigin: "anonymous", body: true }); head.script.push({ src: "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js", integrity: "sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1", crossorigin: "anonymous", body: true }); head.script.push({ src: "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js", integrity: "sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM", crossorigin: "anonymous", body: true }); // Add a meta tag head.meta.push({ name: "keywords", content: "HTML,CSS,XML,JavaScript" }); }
чтобы добавить файлы Bootstrap CSS и JavaScript в тег head
наших страниц.
Затем, чтобы создать наш статический сайт, мы запускаем gridsome build
. Наш сайт будет построен в папке dist
. Затем для обслуживания нашего сайта мы используем веб-сервер Browser Sync. Запускаем npm i -g browser-sync
, чтобы установить сервер, затем переходим в папку dist
и запускаем browser-sync
.
Тогда у нас должно получиться: