Contentful CMS plugin for spike
Note: This project is in early development, and versioning is a little different. Read this for more details.
If you're using Contentful and would like to pull your data for compilation into a spike static site, this will help you out.
npm install spike-contentful -S
This is a standard webpack plugin, but is built for and intended to be used with spike. You can include it in your spike project as such:
// app.js
const Contentful = require('spike-contentful')
const htmlStandards = require('reshape-standard')
const locals = {}
module.exports = {
plugins: [
new Contentful({
addDataTo: locals,
accessToken: 'xxx',
spaceId: 'xxx',
contentTypes: [
{
name: 'posts',
id: '633fTeiMaxxxxxxxxx'
},
{
name: 'authors',
id: '223gTahLaxxxxxxxxx'
}
]
})
],
reshape: htmlStandards({ locals: () => locals })
}
At a minimum, the spike-contentful
plugin requires both name
and id
for contentTypes
. The name
corresponds with how you'd like to access it in your templates. The id
is found listed under "Identifier" by logging into Contentful and clicking "APIs" then "Content model explorer".
Since Spike uses reshape
, you can use a variety of different plugins to expose local variables to your html. We are using reshape along with spike html standards here because it's the plugin provided in spike's default template, and also is currently the only plugin that provides the ability to run complex loops through objects.
In order to pass the data correctly, you must pass spike-contentful
an object, which it will load the data onto when the compile begins under a contentful
key. If you also pass the same object to whatever reshape
plugin you are using in whatever manner it requires to make the data available in your html templates, the data will be present on that object before they start compiling. This is a slightly unconventional pattern for Javascript libraries, but in this situation it allows for maximum flexibility and convenience.
Once included, it will expose a contentful
local to your markup (.sgr
, .html
, etc...) files, which you can use to iterate through your content types. Based on the example above, the posts
content type will be accessible through contentful.posts
, as such:
// a template file
ul
each(loop='post of contentful.posts')
li {{ JSON.stringify(post) }}
For the sugar-free
<ul>
<each loop="post of contentful.blog">
<li>{{ JSON.stringify(post) }}</li>
</each>
</ul>
To limit the number of results for a given content type:
new Contentful({
addDataTo: locals,
accessToken: 'xxx',
spaceId: 'xxx',
contentTypes: [
{
name: 'posts',
id: '633fTeiMaxxxxxxxxx',
filters: {
limit: 10
}
}
]
})
To order results of a given content type:
new Contentful({
addDataTo: locals,
accessToken: 'xxx',
spaceId: 'xxx',
contentTypes: [
{
name: 'posts',
id: '633fTeiMaxxxxxxxxx',
filters: {
limit: 10,
order: 'sys.createdAt'
}
}
]
})
Contentful returns a lot of associated data and, as a result, we give you the ability to pass your own custom transform
option to each content type allowing you to transform the data however you like before it's sent to your views.
new Contentful({
addDataTo: locals,
accessToken: 'xxx',
spaceId: 'xxx',
contentTypes: [
{
name: 'posts',
id: '633fTeiMaxxxxxxxxx',
transform: post => {
// do your transformation here...
return post
}
}
]
})
Using the template option allows you to write objects returned from Contentful to single page templates. For example, if you are trying to render a blog as static, you might want each post returned from the API to be rendered as a single page by itself.
The template
option is an object with path
and output
keys. The path
is an absolute or relative path to a template to be used to render each item, and output is a function with the currently iterated item as a parameter, which should return a string representing a path relative to the project root where the single view should be rendered. For example:
new Contentful({
addDataTo: locals,
accessToken: 'xxx',
spaceId: 'xxx',
contentTypes: [
{
name: 'posts',
id: '633fTeiMaxxxxxxxxx',
template: {
path: 'templates/post.html',
output: post => {
return `posts/${post.id}.html`
}
}
}
]
})
Your template must use the item
variable as seen below. Also note that this feature will not work if your template is ignored by spike.
<p>{{item.title}}</p>
Using Spike Environments, you can change your default app.js
to use Contentful's preview API by using your project's preview key (in this example, yyy
).
new Contentful({
addDataTo: locals,
accessToken: 'yyy',
preview: true;
spaceId: 'xxx'
})
Then set your app.production.js
to:
new Contentful({
addDataTo: locals,
accessToken: 'xxx',
preview: false;
spaceId: 'xxx'
})
From there, running spike compile
will use the preview API, and spike compile -e production
will use the regular content delivery API.
This can also be accomplished with a single app.js
with the help of Dotenv.
Require Dotenv in your app.js
file, then modify the Contentful call to use your preview key (in this example, yyy
).
const env = process.env.SPIKE_ENV;
new Contentful({
addDataTo: locals,
accessToken: env !== 'production' ? 'yyy' : 'xxx' ,
preview: env !== 'production';
spaceId: 'xxx'
})
Finally, if you'd like to have the output written locally to a JSON file so that it's cached locally, you can pass the name of the file, resolved relative to your project's output, as a json
option to the plugin. For example:
new Contentful({
addDataTo: locals,
accessToken: 'xxx',
spaceId: 'xxx',
contentTypes: [
{
name: 'posts',
id: '633fTeiMaxxxxxxxxx'
}
],
json: 'data.json'
})
You may also choose to have the ouput written specifically for any content type :
new Contentful({
addDataTo: locals,
accessToken: 'xxx',
spaceId: 'xxx',
contentTypes: [
{
name: 'posts',
id: '633fTeiMaxxxxxxxxx',
// JSON output expected for this content type
json: 'posts.json'
},
{
name: 'press',
id: '4Em9bQeIQxxxxxxxxx'
// No JSON output needed for this content type
}
],
// Save all content types data in one file
json: 'alldata.json'
})
By default, this plugin will only fetch data once when you start your watcher, for development speed purposes. This means that if you change your data, you will have to restart the watcher to pick up the changes. If you are in a phase where you are making frequent data changes and would like a more aggressive updating strategy, you can set the aggressiveRefresh
option to true
, and your dreams will come true. However, note that this will slow down your local development, as it will fetch and link all entires every time you save a file, so it's only recommended for temporary use.
To run the tests locally, you'll need to add a test/.env
with your name and token values:
cp test/.env.sample test/.env
accessToken
is derived from "APIs" > "Content Delivery API Keys" in the Contentful admin section.spaceId
is also derived from "APIs" > "Content Delivery API Keys" in the Contentful admin section.
- Details on the license can be found here
- Details on running tests and contributing can be found here