Gridsome+strapi实现个人博客

3,459 阅读9分钟

最近在关注gridsome,顺带按照视频教程来实现了一个案例并成功部署,在此记录一下学习的过程,以免后面遇到什么坑再回来翻一翻。strapi工程源码 gridsome工程源码

Gridsome是什么?

静态网站生成器?

  • 是使用一系列配置、模板及数据,生成静态HTML文件及相关资源的工具
  • 也叫预渲染
  • 审查个好难过的网站不需要类似PHP这样的服务器
  • 只需要放到支持静态资源的Web Server或cdn上即可运行

静态网站的好处

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

常见的静态网站生成器

  • Jekyll(Ruby)
  • Hexo(Node)
  • Hugo(Golang)
  • Gatsby(Node/React)
  • Gridsome(Node/Vue)
  • 另外,Next.js合Nuxt.js也能生成静态网站,但是他们更多被认为是SSR框架。 这类静态网站生成器也叫JAMStack,本质上是一种胖前端,调用各种API来实现更多的功能,其实也是一种前后端的模式,只不过离得比较开,甚至前后端来自多个不同的厂商。

静态应用的使用场景

  • 不适合有大量路由页面的应用
    • 如果站点内有成百上千条路由页面,则预渲染将非常缓慢。当然,如果每次更新只需要做一次,但是可能要花一些时间。大多数人不会最终获得数千条静态路由页面,而只是以防万一。
  • 不适合有大量动态内容的应用
    • 如果渲染路线中包含特定于用户查看其内容或其他动态资源的内容,则应确保具有可以展示的占位符组件,知道动态内容加载到客户端为止,否则可能会比较怪异。

创建Gridsome项目

首先电脑里要有python环境,其次配置sharp和libvips的地址,因为sharp中包含一些c++文件,并且其依赖包中的libvips包比较大,国内很难安装成功,所以最好是配置一下这两个包的国内地址。

npm config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"
npm config set sharp_libvips_binary_host "https://npm.taobao.org/mirrors/sharp-libvips"

之后还需要对sharp中的c++文件进行编译,需要下载node-gyp包,此时是需要有python环境的

npm install -g node-gyp

之后就可以创建Gridsome的项目了

gridsome create my-gridsome-site

此时会自动安装依赖,我们可以打断自己重新安装node_modules。之后运行npm run develop启动项目的开发模式来展示页面效果,其中pages下的两个.vue文件对应的就是相应的路由。
运行npm run build则会打包整个项目,生成静态的预渲染出来的html文件。

Gridsome基础-Pages

新建页面

有两种方式可以创建页面。
1、使用文件系统在src目录下创建对应的.vue格式的文件,gridsome会自动生成相应的路由配置,并自动转换成小写。
2、还可以使用相应的API来创建组件。在gridsome.server.js文件中,调用createPage方法生成路由。

module.exports = function (api) {
    api.loadSource(({ addCollection }) => {
        // Use the Data Store API here: https://gridsome.org/docs/data-store-api/
    })

    api.createPages(({ createPage }) => {
        // Use the Pages API here: https://gridsome.org/docs/pages-api/
        createPage({
            path: '/my-page',
            component: './src/templates/MyPage.vue'
        })
    })
}

动态路由

动态路由的创建也有两种方式。
1、基于文件的动态路由:

src/pages/user/[id].vue => /user/:id
src/pages/user/[id]/settings.vue => /user/_id/settings

2、调用API的方式创建。gridsome.server.js中调用:

api.createPages(({ createPage }) => {
    createPage({
        path: '/user/:id(\\d+)',
        component: './src/templates/User.vue'
    })
})

生成路由重写规则

因为/user/:id的路由最终会生成路径为/user/_id.html的文件,而普通的静态web服务器是不会进行处理的,所以要由我们自己去重写动态路由的规则。

集合

假如我们有一个接口,返回一个数组,数组中的每一项为一个对象,将整个数组中的数据展示在页面中,这个是很简单的。

[
    {
        userid: '1',
        id: 1,
        title: 'title',
        body: 'body'
    }
]

但是这种动态获取的数据展示在页面中,是由客户端动态渲染的,并不是预渲染生成的静态页面效果,此时就需要用到集合Collections
同样在gridsome.server.js中调用loadSource方法添加集合,此时的数据调用的是jsonplaceholder的测试接口。

const axios = require('axios')
module.exports = function (api) {
    api.loadSource(async ({ addCollection }) => {
        const collection = addCollection('Post')
        const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts')
            for (const item of data) {
                collection.addNode({
                    id: item.id,
                    title: item.title,
                    content: item.body
                })
            }
        })
    }
}

每个collections都会向GraphQL schema中新增两个字段:postallPostpost是根据id来获取单个节点,allPost是获取整个所有的节点。在启动开发服务之后,会有一个GraphQL的资源管理器,在里面可以查询集合里面的数据。当然,在页面中还是需要使用<page-query></page-query>标签来获取GraphQL中的数据。

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

此时,从GraphQL中获取的数据post会被写入到计算属性的$page中,可以直接在页面中使用:

<template>
    <Layout>
        <div>
            <h1>Post2 Page</h1>
            <ul>
                <li v-for="edge in $page.post.edges"
                    :key="edge.id">
                    <g-link :to="edge.node.path">{{edge.node.title}}</g-link>
                </li>
            </ul>
        </div>
    </Layout>
</template>
<page-query>
query {
    post: allPost {
        edges {
            node {
                id
                title
                path
            }
        }
    }
}
</page-query>
<script>
export default {
    name: 'Post2Page'
}
</script>

还可以给定一个参数,来根据参数查询数据。

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

在GraphQL中变量要以开头,所以上面的查询参数开头,所以上面的查询参数id: ID!表示为参数是ID类型,且不为空。 获取数据之后可以使用$page来使用。

<div>{{ $page.post.content}}</div>

Gridsome案例

首先我们还是使用gridsome create blog-with-gridsome的命令创建并启动项目,之后使用github中的开源模板,最好是将其fork到自己的账户当中,防止模板文件被作者删除或者做一些其他的修改。

模板的处理

模板下载下来之后要把其中引用的资源加到gridsome项目中,其中有bootstrap@fortawesome/fontawesome-free两个包,还有两个字体文件要在css中引入并在main.js中导入进来。新建src/assets/css/index.css文件,在该文件中引入字体资源:

@import url("https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700itali");
@import url("'https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800")

在模板中,其实每个页面的头部导航和尾部都是相同的,可以将其放在默认的布局组件中,中间的内容部分可以分别放在不同的组件中。所以最终的Layout布局组件大概内容为:

<template>
	<div class="layout">
    	<nav>
            ...
        </nav>
        <!-- 子组件出口 -->
        <slot />
        <footer>
            ...
        </footer>
    </div>
</template>

相关的模板完成之后,接下来会使用gridsome的方式来构建纯静态的博客网站。

本地md文件管理文章内容

对博客来说,最重要的功能当然是展示文章

strapi

strapi本身就是一个先进的内容管理框架,可以轻松的生成我们所需要的API。
下面创建本地的strapi应用:

npx create-strapi-app my-project --quickstart

之后会自动打开strapi的注册页面,直接注册就好,注册完成之后就会进入系统的后台。

之后在内容类型生成器中点击创建一个新的Content Type来创建一个集合,有了集合,就可以管理我们的数据了。数据添加完之后不要忘记设置用户的权限,对public权限类型设置count find findone的权限就可以了,在勾选权限时,右侧会出现对应的接口名称。

注意此处需要科学上网,否则保存不上的

设置完成之后就可以测试对应接口的数据。

http://localhost:1337/posts/count

如果想在strapi中使用graphql的方式开获取数据,则需要执行下列操作:

yarn strapi install graphql

安装完之后运行npm run develop启动

注意此时启动的是my-project项目,并不是gridsome创建的项目!

启动成功之后,打开http://localhost:1337/graphql就可以使用graphql来查询strapi中的数据了。

关于数据预取

要将strapi集成到gridsome中,需要使用@gridsome/source-strapi的一个插件。

// 注意此时是在gridsome项目中的。
npm install @gridsome/source-strapi

之后在gridsome的配置文件gridsome.config.jsplugins中添加如下代码:

{
    use: '@gridsome/source-strapi',
        options: {
        apiURL: 'http://localhost:1337',
            queryLimit: 1000,
                contentTypes: ['post'],
                    loginData: {
            identifier: '',
                password: ''
        }
    }
}

最后重新启动应用npm run develop,目的是使刚修改的配置生效,从strapi中获取到数据

注意这里同时启动两个项目,因为gridsome项目查询的数据是从strapi中获取的,
而strapi中的数据是通过my-project项目从strapi的后台系统拿到的。
如果此时在strapi的后台添加一条数据,在gridsome中是查不到的,因为预渲染是要提前拿到数据再渲染到页面上的,因为加数据是预渲染完成之后的操作,所以预渲染的环节是拿不到新增的数据的,若要解决这个问题,重启下gridsome服务就好了。

关于分页

Gridsome提供了@paginate的api来实现分页的功能。可以自定义页数和每页条数。

query ($page: Int) {
    posts: allStrapiPost (perPage: 1, page: $page) @paginate {
        edges {
            node {
                id
                title
                created_at
                createds {
                    id
                    firstname
                    lastname
                }
                tags {
                    id
                    title
                }
            }
        }
    }
}

Gridsome还提供了分页组件Pager,需要像正常组件一样先注册再使用,还需要在page-query中添加查询语句:

query ($page: Int) {
    posts: allStrapiPost (perPage: 1, page: $page) @paginate {
        pageInfo {
            totalPages
            currentPage
        }
        ...
    }
}

页面中则要给pager组件添加一个属性才可以正常使用,这个组件是没有添加样式的,可以根据我们的需要来自己配置,至于页面的话此处不在多说。

<pager :info="$page.posts.pageInfo"></pager>

关于部署

gridsome项目即博客应用本身可以部署在任何支持静态文件的web服务里,而strapi应用需要有node环境。因为gridsome项目在启动时需要请求strapi的服务来获取预渲染的数据,所以要先部署strapi的应用。

strapi默认是使用的sqlite来做数据存储,也可以使用诸如mysql mongodb等其他的数据库,在官网都有相应的配置项,这里使用mysql实现。

要想使用mysql,首先要在服务器里面有mysql的环境,其次修改strapi项目的配置文件config\database.js。配置文件修改如下:

module.exports = ({ env }) => ({
    defaultConnection: 'default',
    connections: {
        default: {
            connector: 'bookshelf',
            settings: {
                client: 'mysql',
                host: env('DATABASE_HOST', 'localhost'),
                port: env.int('DATABASE_PORT', 3306),
                database: env('DATABASE_NAME', 'blog'),
                username: env('DATABASE_USERNAME', 'root'),
                password: env('DATABASE_PASSWORD', ''),
            },
            options: {},
        },
    },
});

这里host使用的是本机地址,是因为我这里的mysqlstrapi是在同一台服务器上,如果在不同的服务器的话,这里要写成其他服务器的地址,其次要有一个名字为blog的数据库,否则部署时会报错找不到对应数据库的。

部署的过程也很简单,直接在云服务器上把源码clone下来,然后运行npm install npm run build等命令,直接打包就好,但是这种方式有个缺点,就是当前连接断掉之后,服务也就挂了,所以这里使用pm2来执行npm命令。

gridsome使用的是vercel来构建部署的。vercel可以支持deploy hooks,可以在strap添加数据之后自动触发构建。但是这种也有缺点,数据量小是合理的,但数据如果多的话会触发多次构建,这个只能说是各有优略。