背景
microCMSを使用する際に、 無料プランだとデータ転送量が気になるので、 画像データをビルド結果に含めたい。
また、ダウンロードした画像はGraphQLから、
Sharp を使った画像編集や gatsbyImageData
の取得も行いたい。
問題点
microCMSの公式プラグインは、 画像データがSharpに対応していない。 また、2022年8月29日以降は更新されていない。 おそらく、Sharpに対応するには、API(モデル)のスキーマが必要になるが、 microCMSのWeb APIからスキーマを取得することができないのが原因だと考えられる(このドキュメントに沿って管理画面からエクスポートすることは可能)。 ちなみに、ContentfullやStrapiはWeb APIからスキーマを取得することができるためSharpに対応している。
https://www.contentful.com/developers/docs/concepts/data-model/ https://docs.strapi.io/dev-docs/backend-customization/models
成果(gatsby-node.js)
microCMSの画像ファイルを自動でSharpで加工可能な形式にする。
たとえば sample-posts
というAPIに、
sampleImage
という画像のフィールドが設定されている場合
{
"sampleImage": {
"url": "https://images.microcms-assets.io/assets/xxxx/yyy/zzz.jpg",
"width": 640,
"height": 480,
}
}
以下のようなGraphQLにより、
gatsbyImageData
で使用するデータを取得することができる
{
allMicrocmsSamplePosts(sort: [{createdAt: DESC}]) {
localSampleImage {
childImageSharp {
gatsbyImageData(width: 600, layout: FIXED)
}
}
}
}
}
そのための gatsby-node.js
がこちら。
const config = require('./gatsby-config')
const { createRemoteFileNode } = require('gatsby-source-filesystem')
const axios = require('axios')
const camelCase = require('camelcase');
const {
apiKey: microcmsApiKey,
serviceId: microcmsServiceId,
apis: microcmsApis
} = config.plugins
.filter(plugin => plugin.resolve === 'gatsby-source-microcms')[0]
.options
const microcmsPrefix = 'Microcms'
const localPrefix = 'local'
const ignoreFields = []
/**
* フィールドがmicroCMSの画像型か判定する
* @param {*} field
* @returns
*/
const isMicroCmsImageField = field => {
return (
field &&
typeof field === 'object' &&
field.url &&
field.width &&
field.height
)
}
/**
* フィールドがmicroCMSの画像配列型か判定する
* @param {*} field
* @returns
*/
const isMicroCmsImageListField = field => {
if (!Array.isArray(field)) {
return false;
}
if (field.length === 0) {
return false
}
for (const el of field) {
if (!isMicroCmsImageField(el)) {
return false
}
}
return true
}
exports.createSchemaCustomization = async ({ actions }) => {
const { createTypes } = actions
/**
* microCMSにはスキーマを取得するAPIがないため
* 全てのコンテンツを取得して画像型のフィールドを検索する必要がある
*/
for (const api of microcmsApis) {
const endpoint = api.endpoint
const url = `https://${microcmsServiceId}.microcms.io/api/v1/${endpoint}`
let offset = 0
const limit = 10
const imageFields = []
const imageListFields = []
while (true) {
const res = await axios.get(url, {
params: { offset, limit },
headers: {
'X-MICROCMS-API-KEY': microcmsApiKey,
}
})
for (const content of res.data.contents) {
for (const field in content) {
if (ignoreFields.includes(field)) {
continue
}
if (imageFields.includes(field)) {
continue
}
if (imageListFields.includes(field)) {
continue
}
if (isMicroCmsImageField(content[field])) {
imageFields.push(field)
continue
}
if (isMicroCmsImageListField(content[field])) {
imageListFields.push(field)
continue
}
}
}
offset += limit
if (offset >= res.data.totalCount) {
break
}
}
for (const field of imageFields) {
const typeName = camelCase(
[microcmsPrefix, endpoint],
{ pascalCase: true }
)
const fieldName = camelCase(
[localPrefix, field],
{ pascalCase: false }
)
createTypes(`
type ${typeName} implements Node {
${fieldName}: File @link(from: "fields.${fieldName}.id")
}
`)
}
for (const field of imageListFields) {
const typeName = camelCase(
[microcmsPrefix, endpoint],
{ pascalCase: true }
)
const fieldName = camelCase(
[localPrefix, field],
{ pascalCase: false }
)
createTypes(`
type ${typeName} implements Node {
${fieldName}: [File] @link(from: "fields.${fieldName}.id")
}
`)
}
}
}
exports.onCreateNode = async ({ node, actions, createNodeId, cache }) => {
const { createNode, createNodeField } = actions
if (node.internal.owner === 'gatsby-source-microcms') {
await Promise.all(Object.keys(node)
.map(async field => {
if (ignoreFields.includes(field)) {
return
}
// *** フィールドが画像型の場合 ***
if (isMicroCmsImageField(node[field])) {
console.log(`start: ${node[field].url}`);
return createRemoteFileNode({
url: node[field].url,
parentNodeId: node.id,
cache,
createNode,
createNodeId,
}).then(fileNode => {
console.log(`end: ${node[field].url}`);
const name = camelCase(
[localPrefix, field],
{ pascalCase: false }
)
return createNodeField({
node,
name,
value: fileNode,
})
})
}
// *** フィールドが画像型リストの場合 ***
if (isMicroCmsImageListField(node[field])) {
return Promise.all(node[field].map(img => {
console.log(`start: ${img.url}`);
return createRemoteFileNode({
url: img.url,
parentNodeId: node.id,
cache,
createNode,
createNodeId,
}).then(() => console.log(`end: ${img.url}`))
})).then(fileNodes => {
const name = camelCase(
[localPrefix, field],
{ pascalCase: false }
)
return createNodeField({
node,
name,
value: fileNodes,
})
})
}
}))
}
}