{"id":21352062,"url":"https://github.com/static-dev/spike-collections","last_synced_at":"2025-07-12T20:32:07.810Z","repository":{"id":57367482,"uuid":"68230788","full_name":"static-dev/spike-collections","owner":"static-dev","description":"jekyll-esque dynamic content for spike","archived":false,"fork":false,"pushed_at":"2019-02-05T12:43:44.000Z","size":1113,"stargazers_count":18,"open_issues_count":11,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-07T22:37:20.782Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/static-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"contributing.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-09-14T18:07:35.000Z","updated_at":"2018-11-06T04:40:55.000Z","dependencies_parsed_at":"2022-08-23T20:10:49.322Z","dependency_job_id":null,"html_url":"https://github.com/static-dev/spike-collections","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/static-dev%2Fspike-collections","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/static-dev%2Fspike-collections/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/static-dev%2Fspike-collections/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/static-dev%2Fspike-collections/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/static-dev","download_url":"https://codeload.github.com/static-dev/spike-collections/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225834367,"owners_count":17531471,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-22T03:12:26.536Z","updated_at":"2024-11-22T03:12:27.169Z","avatar_url":"https://github.com/static-dev.png","language":"JavaScript","readme":"# Spike Collections\n\n[![npm](https://img.shields.io/npm/v/spike-collections.svg?style=flat-square)](https://npmjs.com/package/spike-collections)\n[![tests](https://img.shields.io/travis/static-dev/spike-collections.svg?style=flat-square)](https://travis-ci.org/static-dev/spike-collections?branch=master)\n[![dependencies](https://img.shields.io/david/static-dev/spike-collections.svg?style=flat-square)](https://david-dm.org/static-dev/spike-collections)\n[![coverage](https://img.shields.io/coveralls/static-dev/spike-collections.svg?style=flat-square)](https://coveralls.io/r/static-dev/spike-collections?branch=master)\n\nSome jekyll-esque features for spike\n\n\u003e **Note:** This project is in early development, and versioning is a little different. [Read this](http://markup.im/#q4_cRZ1Q) for more details.\n\n### Why should you care?\n\nHi there! Coming from jekyll to check this out? You've found your happy place. This plugin adds more or less all the essential features from jekyll to spike, giving you the capability to use front matter, date format posts, and paginate. If you are working on a small blog, this is a great plugin for you. And if you're working on a large blog, you should use a real database to store your posts, rather than a github repo 😁\n\n### Installation\n\nInstall with: `npm install spike-collections -S`\n\n\u003e **Note:** This project is compatible with node v6+ only\n\nAdd to your spike project as such:\n\n```js\nconst Collections = require('spike-collections')\nconst htmlStandards = require('reshape-standard')\n\nconst locals = {}\nconst collections = new Collections({ addDataTo: locals })\n\nmodule.exports = {\n  // your config here...\n  reshape: htmlStandards({\n    locals: (ctx) =\u003e collections.locals(ctx, locals)\n  }),\n  plugins: [collections]\n}\n```\n\nThis default configuration will look for a folder called `posts` and compile all the content into that folder in the same way that jekyll does. You can also customize your collections. For example, the default config really resolves to this:\n\n```js\nconst collections = new Collections({\n  addDataTo: locals,\n  collections: {\n    posts: { files: 'posts/**' }\n  }\n})\n```\n\nSo you can rename your collection, put it in a different folder, add multiple other collections, etc. Just add the name of the collection as the key, and a globstar string as the value.\n\nNote that this plugin interacts with your locals in two separate places. First, it takes the entire object in the `addDataTo` param, through which it adds all your posts to be accessible to any page in your app. Second, it uses the `collections.locals` function inside the reshape configuration in order to be able to add local variables which vary per-post, like those in the frontmatter, the date, etc.\n\nSee [options](#options) for more detail on further configuring spike-collections.\n\n### Usage\n\nTo get started, just make a `posts` folder (or another name, but adjust the `collections` option), and put a file in there. So for example:\n\n```\n.\n└── posts\n    ├── hello-world.sgr\n    └── second-post.sgr\n```\n\nWithin each file, you can use yaml front matter to designate metadata, as such:\n\n```jade\n---\ntitle: 'Intro Post'\nauthor: 'Jeff'\n---\n\nextends(src='_post_layout.sgr')\nblock(name='content')\n  # Hello world!\n\n  This is my first post. What a **cool** post!\n```\n\nIn this example, we assume that a layout has been configured for the post. Posts are written in reshape syntax because it makes them much more powerful. Instead of having no power to add arbitrary html or define different sections of the page, as it would be with pure markdown, with reshape, you can modify anything you need. Here's how the layout may look:\n\n```jade\ndoctype html\nhtml\n  head\n    title {{ title }}\n  body\n    h1 {{ title }}\n    h3.author by {{ author }}\n    .content(md)\n      block(name='content')\n```\n\nAll of your views will have get a `_collections` variable as well, which holds information on all of your collections. Each collection will be scoped as the name you gave it under `_collections`. So `{{ _collections.posts }}` would return the default posts folder. You can use this for building an index page as such:\n\n```jade\nextends(src='layout.sgr')\nblock(name='content')\n  h1 My Cool Blog\n  ul\n    each(loop='post of _collections.posts')\n      li: a(href='{{ post._path }}') {{ post.title }}\n```\n\nWith this in place, you've got a basic blog structure going!\n\n#### Special Variables\n\nSpike collections will add a few special variables to each post for developer convenience:\n\n- `_path`: the full output path to the current post\n- `_collection`: the name of the collection of the current post\n\n#### Transform\n\nThis is not a feature that jekyll includes as far as I know, but can be incredibly useful and powerful when used correctly. Adding a `transform` function allows you to make a transformation of your choice to the locals of each post in each of your collections. That is to say, before rendering the page, each page's locals are run through the function you provide to `transform`, and you are given the chance to modify them as you wish.\n\nThis could be used to pick out specific posts or all posts from specific collections and make changes to their frontmatter, to add defaults across the board to all posts or specific collections, etc. The function can also be asynchronous and return a promise, although this could greatly slow down your compile time if you have many posts so be careful. For example, here's how to add a default variable (`reaction`, as `wow`) across all posts in a collection called `doges`:\n\n```js\nconst collections = new Collections({\n  addDataTo: locals,\n  collections: {\n    doges: {\n      files: 'doges/**',\n      transform: (data) =\u003e {\n        console.log(data)\n        data.reaction = 'wow'\n        return data\n      }\n    }\n  }\n})\n```\n\nIn this case, every post in `doges` will have the same `reaction` without you having to repeat it in all the frontmatter. This is a very simple example, and there are much more powerful things you can do if you want, so feel free to experiment! Just return an object or promise for an object containing the frontmatter and you're set.\n\n#### Permalinks\n\nThe permalinks function is specifically for modifying the output path of your posts, much like the equivalent function in jekyll, although spike-collections' version has a bit more power because it's a full function rather than a template string.\n\nThe `permalinks` function will receive the file's path as the first argument, and the full frontmatter locals as an optional second argument, from these two you can build your ideal output url and return it. By default, the output url will be the same as the url specified in the source.\n\nAn example of an implementation of jekyll's default `date` output format:\n\n```js\nconst collections = new Collections({\n  addDataTo: locals,\n  collections: {\n    posts: {\n      files: 'posts/**',\n      permalink: (p) =\u003e {\n        // matches: [1] collection, [2] year, [3] month, [4] day, [5] title\n        const m = p.match(/^([A-Za-z-_])+\\/(\\d+)-(\\d+)-(\\d+)-([A-Za-z-_]+)\\./)\n        if (!m || !m[1] || !m[2] || !m[3] || !m[4]) {\n          throw new Error(`incorrect title formatting for post: ${path}`)\n        }\n        return `${m[0]}/${m[1]}/${m[2]}/${m[3]}/${m[4]}.html`\n      }\n    }\n  }\n})\n```\n\nNote that slashes in the output are translated to folders as expected.\n\nWhile this may look verbose, it is left up to the developer to add more flexibility to path parsing if desired. This means it's not required that the date come first, or even that hyphens are used to separate pieces. That being said, functions that exactly match jekyll's default formats (minus `pretty`, which is easily implemented through netlify or nginx) can be accessed by pulling them off the `Collections` class as such:\n\n```js\nconsole.log(Collections.jekyll)\n// =\u003e `date`, `ordinal`, and `none` functions can be used\n// =\u003e also can use `regex` to get our jekyll format parser\n```\n\nSo a much shorter way to implement the previous example would be:\n\n```js\nconst collections = new Collections({\n  addDataTo: locals,\n  collections: {\n    posts: {\n      files: 'posts/**',\n      permalink: Collections.jekyll.date\n    }\n  }\n})\n```\n\n#### Pagination\n\nThe `pagination` option allows posts to be paginated in a relatively flexible manner. When pagination is turned on, a couple things happen:\n\nYour locals, if provided through `addDataTo`, get an additional `_pages` variable, which is an array of objects like this:\n\n```js\n[\n  {\n    page: 1,\n    path: 'posts/page/1.html',\n    posts: ['posts/welcome.html', 'posts/another.html']\n  }, {\n    page: 2,\n    path: 'posts/page/2.html',\n    posts: ['posts/more.html', 'posts/wowsuchpost.html']\n  }\n]\n```\n\nAlso, while your individual posts stay the same, a number of additional templates are rendered out according to the `pagination.template` option, which is a path to a template relative to the root. These templates will display subsequent pages of posts.\n\nWithin the template, you will have access to a `_pages` variable as a local, which lists all pages. They also get `_currentPage` which is just the object according to the page being rendered, and a `_next` and `_previous` variable, which are the URLs to the next and previous pages.\n\nEach individual post gets a `_page` variable added to their rendered front matter, which is a number - the page that post is on.\n\nAlso note that you can pass a `perPage` option to the `pagination` object to define the number of posts you want to be rendered to each page.\n\nHere's an example of how a collection with pagination enabled might look in your configuration:\n\n```js\nnew Collections({\n  addDataTo: locals,\n  collections: {\n    posts: {\n      files: 'posts/**',\n      paginate: {\n        template: 'posts/_template.sgr',\n        perPage: 5,\n        output: (i) =\u003e `posts/page${i}.html`\n      }\n    }\n  }\n})\n```\n\nMore information on pagination options can be found below.\n\n### Using Raw Markdown Files\n\nWe strongly recommend using reshape templates with a markdown block when trying to write markdown due to the higher flexibility of using full html templates. However, there are some applications such as porting from other systems that require the use of raw markdown files without html layouts at all, and some people just still prefer this method. Spike Collections can handle this case if it is needed using the `markdownLayout` option. For example:\n\n```js\nnew Collections({\n  addDataTo: locals,\n  collections: {\n    posts: {\n      files: 'posts/**',\n      markdownLayout: 'layouts/post.html'\n    }\n  }\n})\n```\n\nThe `markdownLayout` option is an accepts a glob-compatible path, resolved relative to the project root, pointing to a reshape layout. All front matter locals will be available within the layout, and the contents of the markdown file will be available via an automatically injected `_content` local, rendered as html. An example of how a layout might look:\n\n```\n\u003cdoctype html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003e{{ title }}\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003e{{ title }}\u003c/h1\u003e\n    {{{ _content }}}\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nNote that if the `markdownLayout` option is present, any file with a `.md` or `.markdown` extension in your collection folder will be rendered into the layout. Any other file will be rendered as usual.\n\n### Options\n\n| Name | Description | Default |\n| ---- | ----------- | ------- |\n| **addDataTo** | An object that will have collections' data appended to it | |\n| **collections** | An object with the keys being the name of your collection, and values as listed below | `{ posts: { files: posts/** } }` |\n| **collections.[name].files** |  A [globstar](http://globtester.com) string relative to the project root matching any files to be processed as posts | |\n| **collections.[name].permalinks** | A function that accepts the relative path to a given file and returns a desired output path. | |\n| **collections.[name].transform** | A function that accepts the full locals for each post and returns a modified locals object. | |\n| **collections.[name].markdownLayout** | Path relative to the project layout specifying a layout file to render any raw markdown files into | |\n| **collections.[name].paginate** | Object with keys as described below | |\n| **collections.[name].paginate.perPage** | Integer representing the number of posts per page. | `10` |\n| **collections.[name].paginate.template** | _(required if paginate is provided)_ Path (relative to the project root) to a template to render additional pages into. | |\n| **collections.[name].paginate.output** | _(required if paginate is provided)_ A function that takes a page number and must output a relative destination path for the page | |\n\n### What About Drafts?\n\nDrafts are just a folder of content that is ignored and not compiled. It's easy in spike to replicate this functionality using the `ignores` configuration. To create a drafts folder, you can do something like this:\n\n```js\nmodule.exports = {\n  // ...other config..\n  ignores: ['drafts/**']\n}\n```\n\nThis will ensure that none of your draft posts are published. To have your drafts compiled, simply remove it from the array, and add the same pattern as a collection.\n\n### A Note on Speed\n\nThis is not a fast plugin. It does a lot of reading, writing, and transforming of files. If you are planning on running a large blog with hundreds or thousands of posts, you will probably see the compile time start to suffer. If this is this case, we'd recommend getting your data out of flat files and into a database. Whenever you are dealing with a large volume of data, a database is usually a better bet anyway. Build a light API, hook it up to your database, and pull it in to spike using [spike-records](https://github.com/static-dev/spike-records) instead!\n\n### License \u0026 Contributing\n\n- Details on the license [can be found here](LICENSE.md)\n- Details on running tests and contributing [can be found here](contributing.md)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstatic-dev%2Fspike-collections","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstatic-dev%2Fspike-collections","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstatic-dev%2Fspike-collections/lists"}