Vue静态站点生成 -- GridSome基础

本章用静态站点生成器 Gridsome 快速生成静态网站,通过简单的案例学习其相关知识点

静态网站生成器

静态网站生成器是使用一系列配置、模板以及数据,生成静态 HTML 文件及相关资源的工具。

特点

  • 由于是提前生成静态 HTML 文件及相关资源,所以也叫预渲染
  • 生成的网站不需要类似 PHP 这样的服务器,只需要放到支持静态资源的 web serverCDN 即可运行

优势

  • 省钱-不需要专业的服务器,只要能托管静态文件的空间
  • 快速-不经过后端服务器的处理,只传输内容
  • 安全- 没有后端程序的执行,自然会更安全

常见的静态网站生成器

  • 基于 Ruby 的 JekyII
  • 基于 Node.js 的 Hexo
  • 基于 GoLang 的 Hugo
  • 基于 Node/React 的 Gatsby
  • 基于 Node/Vue 的 Gridsome
  • 基于 Vue 的 VuePress

Next.js 和Nuxt.js 也能生成静态网站,但是他们更多的被认为是服务端渲染(SSR)框架

这类静态网站生成器统称 JAMStack(JAM指JavaScript、API、Markup),通过调用各种API来实现更多的功能,其本质是一种前后端分离的模式,甚至前后端可以来自不同的厂商。

静态应用的使用场景

  • 不适合有大量路由页面的应用,路由页面过多,则预渲染会非常慢

如果您的站点有成百上千条路由页面,则预渲染将非常缓慢。当然,你每次更新只需要做一次,但是可能要花一些时间。大多数人不会最终获得数千条静态路由页面,而只是以防万一。

适合纯内容展示类的应用,例如博客,企业的宣传站,包括一些文档类的,这些网站完全可以做成静态应用,得到有个极致的速度体验。

  • 不适合有大量动态内容的应用例如后台管理系统

如果渲染路线中包含特定于用户查看其内容或其动态源的内容,则应确保您具有可以显示的占位符组件,直到动态内容加载到客户端为止。否则可能有点怪异。

Gridsome

学习 GridSome 要有 Vue 基础,但比 Vue 简单,参考 官方文档或者GitHub gridsome

Gridsome介绍

  • 一个免费、开源、基于 Vue.js 技术栈的静态网站生成器。
  • Gridsome 是由 Vue.js 驱动的 Jamstack 框架,用于构建默认情况下快速生成的静态生成的网站和应用。
    Jamstack使您可以通过预渲染文件并直接从CDN直接提供文件来构建快速安全的站点和应用程序,而无需管理或运行Web服务器。
  • Gridsome 是 Vue 提供支持的静态站点生成器,用于为任何无头 CMS,本地文件或API构建可用于CDN的网站
  • 使用Vue.js,webpack和Node.js等现代工具构建网站。通过npm进行热重载并访问任何软件包,并使用自动前缀在您喜欢的预处理器(如Sass或Less)中编写CSS。
  • Gridsome 使开发人员可以轻松构建默认情况下快速生成的静态生成的网站和应用程序
  • Gridsome允许在内容里面引用任何CMS或数据源。
    从WordPress,Contentful或任何其他无头CMS或API中提取数据,并在组件和页面中使用GraphQL访问它。

Gridsome 工作原理

  • Gridsome生成静态HTML,一旦加载到浏览器中,该HTML就会渗入Vue SPA。这意味着您可以使用 Gridsome 构建静态网站和动态应用程序。
  • Gridsome为每个页面构建一个.html文件和一个.json文件。加载第一页后,它仅使用.json文件来预取和加载下一页的数据。它还为需要它的每个页面构建一个.js包(代码拆分)。
  • 它使用 vue-router 进行SPA路由,并使用vue-meta来管理<head>
  • Gridsome默认添加最小57kB的gzip JS捆绑包大小(vue.js,vue-router,vue-meta和一些用于图像延迟加载的文件)。

详细了解其工作原理

使用场景

  • 不适合管理系统
  • 简单页面展示
  • 想要有更好的 SEO
  • 想要有更好的渲染性能

创建Gridsome项目

环境准备:Node.js(v8.3 +)环境、C++编译环境、Python环境

安装Gridsome CLI脚手架工具

Gridsome官网

1
2
3
4
5
6
7
8
# 使用 yarn
$ yarn global add @gridsome/cli

# 使用 npm
$ npm install --global @gridsome/cli

# 查看是否安装成功
$ gridsome --version

准备sharp的安装环境

不管是哪个是哪个操作平台(Windows、Linux),都需要有python环境,windows环境可以通过Windows应用商店安装。

sharp官网
sharp:Github地址

使用Gridsome脚手架创建项目时,处理图片(如压缩图片的大小、转换图片的格式)依赖的sharp包很难安装成功,主要原因有两点:

  • sharp里面包含一些C++文件,在安装的时候需要先编译,需要要有C++编译环境
  • sharp依赖的libvips几十兆,比较大,由于国内网络环境原因,很难下载成功
    所以在创建Gridsome项目项目之前首先需要解决这两个问题
  1. 在sharp官网找到国内镜像,安装国内镜像
1
2
3
4
# 安装sharp镜像
$ npm config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"
# 安装sharp_libvips镜像
$ npm config set sharp_libvips_binary_host "https://npm.taobao.org/mirrors/sharp-libvips"
  1. 安装C++编译环境

node-gyp:Github地址
下载Python安装包,安装Python环境
安装node-gyp及相关套件windows-build-tools,它的作用是编译C++扩展包

1
2
3
4
5
# 安装node-gyp
$ npm install -g node-gyp

# 安装windows-build-tools
$ npm install --global windows-build-tools

创建Gridsome项目,并安装依赖包

1
2
# 创建项目
$ gridsome create blog-with-gridsome

创建的目录结构 详见Gridsome官方文档

├── src
│   ├── components     # 公共组件
│   ├── layouts        # 布局组件
│   ├── pages          # 页面路由组件
│   ├── templates      # 模板文件
│   ├── favicon.png    # 网站图标
│   └── main.js        # 应用入口
├── static             # 静态资源存储目录,该目录中的资源不做构建处理
├── README.md
├── gridsome.config.js # gridsome配置文件
├── gridsome.server.js # gridsome内部服务配置文件
├── package-lock.json
└── package.json

注意:
1、Gridsome不需要显示的配置路由,会依据pages目录自动生成路由规则
2、pages目录下的路由组件名默认支持小写,如果是大写,会转成小写,多个单词用”-“连接,如:src/pages/AboutUs.vue 转成 /about-us
3、页面路由会自动查找index.vue组件,如src/pages/blog/Index.vue becomes /blog

npm scripts命令

1
2
3
4
5
"scripts": {
"build": "gridsome build", //编译构建预渲染静态网页
"develop": "gridsome develop", //本地启动项目
"explore": "gridsome explore"
},

本地执行 npm run develop 启动项目,浏览器访问http://localhost:8080/,页面如下:

GridSome

构建预渲染静态网页

1
$ npm run build

构建结果默认输出到 dist 目录中,可以将其部署在任何支持静态文件的web服务器上,Gridsome 会把每个路由文件构建为独立的 HTML 页面。

使用serve插件本地部署测试打包后的文件

serve是一个基于node.js的命令行静态web服务

1
2
# 安装 serve 部署静态网站
$ npm install -g serve

项目根目录下执行 serve dist启动本地服务,如下图表示启动成功:

GridSome

项目配置文件

Gridsome官方文档-配置文件
Gridsome 需要 gridsome.config.js 才能工作。插件和项目设置位于此处。
gridsome.config.js ,基本配置文件如下所示:

1
2
3
4
5
module.exports = {
siteName: 'Gridsome',
siteUrl: 'https://www.gridsome.org',
plugins: []
}
属性 类型 默认值 说明
siteName string <dirname> 该名称通常在标题标签中使用
siteDescription string ‘’ 页面描述,<meta name="description" content="xxx">
pathPrefix string ‘’ Gridsome假定您的项目是从域的根目录提供的。如果您的项目将托管在名为my-app的子目录中,则将此选项更改为“/ my-app”
titleTemplate string %s - <siteName> 设置标题标签的模板。 %s占位符将替换为您在页面中设置的metaInfo的标题
plugins Array [] 通过将插件添加到plugins数组来激活插件
templates object {} 定义 collections 的路由和模板
metadata object {} 将全局元数据添加到GraphQL模式
icon string \ object ‘./src/favicon.png’ Gridsome默认情况下会将位于src / favicon.png的任何图像用作favicon和touchicon,但您可以定义其他路径或大小等。图标应为正方形且至少16个像素。网站图标将调整为16、32、96像素。默认情况下,触摸图标的大小将调整为76、152、120、167、180像素
configureWebpack object \ Function 如果该选项是一个对象,它将与内部配置合并
chainWebpack Function 该函数将接收由webpack-chain驱动的ChainableConfig实例
runtimeCompiler boolean false 在运行时包括Vue模板编译器
configureServer Function 配置开发服务器
permalinks.trailingSlash boolean true 默认情况下,在页面和模板后添加斜杠。启用此选项后,具有动态路由的页面将不包含尾部斜杠,并且服务器上必须具有额外的重写规则才能正常工作。另外,<g-link>的静态路径不会自动包含尾部斜杠,而应包含在路径中
permalinks.slugify 使用自定义的Slugify方法。默认是 @sindresorhus/slugify
css.split boolean false 将CSS分成多个块。默认情况下禁用拆分。拆分CSS可能会导致奇怪的行为
css.loaderOptions Object {} 将选项传递给与CSS相关的 loader
host string localhost 访问地址
port number 8080 端口号
outputDir string ‘dist’ 运行gridsome构建时将在其中生成生产构建文件的目录

插件示例:

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
plugins: [
{
use: '@gridsome/source-filesystem',
options: {
path: 'blog/**/*.md',
route: '/blog/:year/:month/:day/:slug',
typeName: 'Post'
}
}
]
}

注意:开发过程中修改配置需要重启服务

路由组件页面

Gridsome官方文档-页面组件

生成页面组件有两种方式:

  • 单文件组件,使用文件系统的方式,和创建普通文件方式一致
  • 使用 Pages API 以编程方式创建页面
pages 中的单文件组件

src/pages 目录中的单文件组件将自动具有其自己的URL。文件路径用于生成 URL,以下是一些基本示例:

  • src/pages/Index.vue becomes /(The frontpage)
  • src/pages/AboutUs.vue becomes /about-us/
  • src/pages/about/Vision.vue becomes /about/vision/
  • src/pages/blog/Index.vue becomes /blog/

大小自动转小写,驼峰命名会自动使用短横杠分割

src/pages 中的页面通常用于诸如 /about/ 之类的固定 URL,或用于在 /blog/ 等处列出博客文章

使用 Pages API 创建页面

可以使用 gridsome.server.js 中的 createPages 钩子以编程方式创建页面。如果您要从外部 API 手动创建页面而不使用 GraphQL 数据层,则此功能很有用

1
2
3
4
5
6
7
8
module.exports = function (api) {
api.createPages(({ createPage }) => {
createPage({
path: '/my-page',
component: './src/templates/MyPage.vue'
})
})
}

重启本地项目,浏览器访问http://localhost:8080/my-page页面正常,说明创建路由页面成功。

动态路由

动态路由对于仅需要客户端路由的页面很有用。例如,根据URL中的细分从生产环境中的外部API获取信息的页面。

通过文件创建动态路由

动态页面用于客户端路由。可以通过将名称包装在方括号中来将路由参数放置在文件和目录名称中。例如:

  • src/pages/user/[id].vue becomes /user/:id
  • src/pages/user/[id]/settings.vue becomes /user/:id/settings

注意事项:

  • 在构建时,这将生成 user/_id.html 和 user/_id/settings.html,并且您必须具有重写规则以使其正常运行。
  • 具有动态路由的页面的优先级低于固定路由。例如,如果您有一个 /user/create 路由和 /user/:id 路由,则 /user/create 路由将具有优先级。

这是一个基本的页面组件,它使用路由中的id参数来获取客户端的用户信息:

  • user/[id].vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div v-if="user">
<h1>{{ user.name }}</h1>
</div>
</template>

<script>
export default {
data() {
return {
user: null
}
},
async mounted() {
const { id } = this.$route.params
const response = await fetch(`https://api.example.com/user/${id}`)

this.user = await response.json()
}
}
</script>

始终使用 mounted 来获取客户端数据。由于在生成静态HTML时执行数据,因此在 created 中获取数据会引起问题。

通过编程方式创建动态路由

以编程方式创建带有动态路由的页面,以获取更高级的路径。动态参数使用 : 来指定。

每个参数都可以具有一个自定义的正则表达式,以仅匹配数字或某些值

  • gridsome.server.js
1
2
3
4
5
6
7
8
module.exports = function (api) {
api.createPages(({ createPage }) => {
createPage({
path: '/user/:id(\\d+)',
component: './src/templates/User.vue'
})
})
}
生成重写规则

Gridsome无法为动态路由的每种可能的变体生成HTML文件,这意味着直接访问URL时最有可能显示404页。而是,Gridsome生成一个HTML文件,该文件可用于重写规则。例如,类似/user/:id的路由将生成位于/user/_id.html的HTML文件。您可以具有重写规则,以将所有与/user/:id匹配的路径映射到该文件。

由于每种服务器类型都有自己的语法,因此必须手动生成重写规则。 afterBuild 挂钩中的 redirects 数组包含应生成的所有必要的重写规则。

1
2
3
4
5
6
7
8
9
10
11
const fs = require('fs')

module.exports = {
afterBuild ({ redirects }) {
for (const rule of redirects) {
// rule.from - The dynamic path
// rule.to - The HTML file path
// rule.status - 200 if rewrite rule
}
}
}
页面 meta 信息

Gridsome 使用 vue-meta 处理有关页面的元信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<h1>Hello, world!</h1>
</div>
</template>

<script>
export default {
metaInfo: {
title: 'Hello, world!',
meta: [
{ name: 'author', content: 'John Doe' }
]
}
}
</script>
自定义 404 页面

创建一个 src/pages/404.vue 组件以具有一个自定义 404 页面

Collections 集合

集合是一组节点,每个节点都包含带有自定义数据的字段。如果您要在网站上放置博客文章,标签,产品等,则集合很有用。

数据集合作用:

  • 承载数据-将通过请求获取到的动态数据预渲染到页面当中
  • 预渲染页面-将集合包含模板的节点预渲染成页面

数据集合原理:

数据集合原理

将从各种数据源获取到的外部数据源,通过某种方式生成集合,集合添加带模板的节点,最后将这些节点预渲染成页面。

在开发和构建期间,这些集合存储在本地内存数据存储中。节点可以来自本地文件(Markdown,JSON,YAML等)或任何外部API。

添加集合
  • source plugins插件,通过封装好的插件将外部动态数据集成到gridsome中
  • 数据存储api(Data Store API)创建数据集合,通过在gridsome.server.js中将外部动态数据提前获取
使用 source plugins 添加集合

将集合添加到 Gridsome 的最简单方法是使用源插件。本示例从 WordPress 网站创建集合。源插件的 typeName 选项通常用于为插件添加的集合名称添加前缀。

  • gridsome.config.js
1
2
3
4
5
6
7
8
9
10
11
module.exports = {
plugins: [
{
use: '@gridsome/source-wordpress',
options: {
baseUrl: 'YOUR_WEBSITE_URL',
typeName: 'WordPress',
}
}
]
}

插件的列表详见plugins

使用 Data Store API 添加集合

您可以从任何外部 API 手动添加集合。

本示例创建一个名为 Post 的集合,该集合从 API 获取内容并将结果作为节点添加到该集合中

  • gridsome.server.js 使用数据存储api创建数据集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const axios = require('axios')

module.exports = function (api) {
api.loadSource(async actions => {
const collection = actions.addCollection('Post')

const { data } = await axios.get('https://api.example.com/posts')

for (const item of data) {
collection.addNode({
id: item.id,
title: item.title,
content: item.content
})
}
})
}

可以通过GraphQL查询集合当中的数据,下面具体看下

GraphQL数据层

GraphQL

GraphQL数据层是在开发模式下可用的工具。这是临时存储到 Gridsome 项目中的所有数据的地方。可以将其视为可帮助您更快更好地处理数据的本地数据库。

来自 GraphQL 数据层的数据将生成为静态内容。

数据层和导入数据的源之间没有实时连接,这意味着您需要重新生成网站以获取最新的数据更新。

如果需要动态数据,则应使用客户端数据

提示:默认情况下,Pages 也 Site metadata 已添加到数据层。

通过GraphQL查询集合当中的数据

每个 collection 都会向GraphQL schema中添加两个用于获取页面节点的根级字段,这些字段用于检索页面中的节点,字段名称是根据集合名称自动生成的,如创建了一个 Post 数据集合,就会向 GraphQL schema 中添加两个根级字段:

  • Post:通过id获取单个节点
  • allPost:获取节点列表

开发环境中,启动项目的时候,同时启动了GraphQL Data资源管理,浏览器访问 http://localhost:8080/___explore(只有开发环境可访问),可以看到 Post 和 allPost

在GraphQL Data资源管理器的左侧区域输入查询条件,点击演示按钮,在右侧区域查看结果,如下查询id为2的文章详情

页面中通过代码查询GraphQL集合当中的数据

  • 在Pages 、Templates中使用<page-query>
  • 在Components中使用<static-query>
  1. 查询节点集合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //查询按标题排序的数据集合
    query {
    allPost(sortBy: "title", order: DESC) {
    edges {
    node {
    title
    }
    }
    }
    }

    //查询按多个字段排序的数据集合
    query {
    allPost(sort: [{ by: "featured" }, { by: "date" }]) {
    edges {
    node {
    title
    }
    }
    }
    }
  2. 查询单个节点

    1
    2
    3
    4
    5
    query {
    post(id: "1") {
    title
    }
    }
  3. 在页面组件中查询节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <template>
    <Layout>
    <h2>Latest blog posts</h2>
    <ul>
    <li v-for="edge in $page.posts.edges" :key="edge.node.id">
    {{ edge.node.title }}
    </li>
    </ul>
    </Layout>
    </template>

    <page-query>
    query {
    posts: allWordPressPost {
    edges {
    node {
    id
    title
    }
    }
    }
    }
    </page-query>
  4. 在其他组件中查询节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <template>
    <div v-html="$static.post.content" />
    </template>

    <static-query>
    query {
    post(id: "1") {
    content
    }
    }
    </static-query>

    创建src/pages/Posts2.vue,使用<page-query>查询GraphQL中的数据集合,通过预渲染实现页面静态化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <template>
    <Layout>
    <div>
    <h1>Posts2</h1>
    <ul>
    <li v-for="edge in $page.posts.edges" :key="edge.node.id">
    <g-link :to="edge.node.path">{{ edge.node.title }}</g-link>
    </li>
    </ul>
    </div>
    </Layout>
    </template>

    <page-query>
    query {
    posts: allPost {
    edges {
    node {
    id
    title
    }
    }
    }
    }
    </page-query>

注意:

1. 修改了gridsome.server.js文件以后,需要重新启动项目,修改的内容才能生效
2. jsonplaceholder提供了很多在线的模拟数据接口,可用于开发阶段的功能测试
3. GraphQL Data对应的资源管理只有在开发模式下能访问
4. Gridsome会把page-query查到的数据放入当前组件实例的$page对象当中

探索可用的类型和字段

可以通过在 GraphQL 资源管理器中打开架构选项卡来浏览可用字段。

阅读有关如何在 GraphQL 中查询节点的更多信息查看Querying data

Templates

模板用于为集合中的节点创建单个页面。节点需要相应的页面才能显示在其自己的URL上。

src/templates下创建一个和集合名字一致的模板,并在gridsome.config.js中为该集合配置创建的模板

  • src/templates/Post.vue创建模板,并接收页面传参id、修改页面标题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<Layout>
<div>
<h1>{{$page.post.title}}</h1>
<h5>{{$page.post.content}}</h5>
</div>
</Layout>
</template>

<page-query>
query($id: ID!) {
post(id: $id) {
id
title
content
}
}
</page-query>

<script>
export default {
name: "PostPage",
//只有函数中可以访问当前组件实例的$page
metaInfo() {
return {
title: this.$page.post.title
}
}
};
</script>

注意:
query ($id: ID!) {} :
id :指的是在 gridsome.config.js 中配置模板时填写的动态路由 :id;
ID! :是指 对应字段 ID,并且是非空的

  • gridsome.config.js配置模板
1
2
3
4
5
6
7
8
9
10
11
module.exports = {
...
templates: {
Post: [
{
path: '/posts/:id',
component: './src/templates/Post.vue'
}
]
}
}
  • src/pages/Posts.vue 配置模板入口
1
2
3
4
5
6
7
...
<ul>
<li v-for="edge in $page.posts.edges" :key="edge.node.id">
<g-link :to="edge.node.path">{{ edge.node.title }}</g-link>
</li>
</ul>
...

重新启动项目,如果能在浏览器正常访问文章详情页面,说明以上使用模板渲染节点页面成功

总结

  • 学习Gridsome需要具备css、html、javascript、vue基础
  • 使用脚手架(@gridsome/cli)创建Gridsome项目时,本地需要有环境准备Node.js(v8.3 +)环境、C++编译环境、Python环境
  • Gridsome不需要显示的配置路由,会依据pages目录自动生成路由规则
  • 每次修改配置文件后,需要重新启动项目,修改的部分才会生效
  • 需要预渲染的外部数据必须通过集合collection获取
  • Gridsome有三种获取动态数据的方式:通过插件、特定的API、本地文件