Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic collections & dayjs for date parsing #17

Merged
merged 3 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions app/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Core files

These four files are the "framework" part of Tropical (if you can call it that), but I encourage you to experiment and change them to suit your needs!

Each [new Tropical version released](https://github.com/bensmithett/tropical/releases) comes with [upgrade instructions](https://github.com/bensmithett/tropical/blob/fde7ad64f181f21abc13d23879b94473af573d03/app/decisions/004_provide_upgrade_instructions-with-new-version-releases.md) so you can selectively bring new features into your site even if you've changed these files.

## `build.js`

A script run in Node.js that:
Expand All @@ -21,8 +25,8 @@ Optional helpers for enabling targeted component [hydration](https://reactjs.org

Prerenders your static site:

- Builds a HTML file from every `.js` or `.mdx` page in `pages`
- Builds a [JSON Feed](https://www.jsonfeed.org/) containing every page with `collection: 'posts'` in its metadata
- Builds a HTML file from every `.js`, `.md` or `.mdx` page in `pages`
- Builds a [JSON Feed](https://www.jsonfeed.org/) for the collection specified in `tropical.feedCollection` in package.json

## Hydration helpers

Expand Down
2 changes: 1 addition & 1 deletion app/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ rimraf(path.resolve(__dirname, '../output/*'), err => {
prerender(manifest, mode)
} catch (err) {
console.error(chalk.red('🏝 Error in prerender function...'))
console.error(chalk.red(err))
console.error(chalk.red(err.stack))
}
}

Expand Down
2 changes: 1 addition & 1 deletion app/pages/example-post.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import image from '../components/ExampleComponent/gunayala.jpg'

import ExamplePostList from '../components/ExamplePostList/ExamplePostList'

<ExamplePostList posts={props.posts} />
<ExamplePostList posts={props.collections.posts} />

---

Expand Down
4 changes: 2 additions & 2 deletions app/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import ExamplePostList from '../components/ExamplePostList/ExamplePostList'
// Note we're importing ExampleComponent wrapped in asIsland, so it will be hydrated in the browser.
import { ExampleComponentIsland } from '../components/ExampleComponent/ExampleComponent'

export default function IndexPage ({ posts }) {
export default function IndexPage ({ collections }) {
return (
<>
<ExampleComponentIsland alertMessage='Hello!' />
<ExamplePostList posts={posts} />
<ExamplePostList posts={collections.posts} />
</>
)
}
75 changes: 49 additions & 26 deletions app/prerender.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
/*
This module's default export (a function called `prerender`) is responsible for creating your HTML files.

This one...

- loops through JS and MDX files in the `pages` directory and renders a HTML file for each.
- creates a JSON Feed
- sets up Fela and Helmet so they can be used in pages and components

...but Tropical doesn't care *how* you generate your HTML files. Change this at your leisure!
The prerender function exported by this module builds your static site from the source in `pages`.
Make it your own! You could alter this function to:
- Generate a sitemap or XML RSS feed
- Remove the JSON Feed if you don't need it
- Build new pages based on different metadata (e.g. tag or date-based archive pages)
*/

import packageJSON from '../package.json'
Expand All @@ -21,6 +17,9 @@ import { RendererProvider } from 'react-fela'
import { renderToMarkup } from 'fela-dom'
import { Helmet } from 'react-helmet'
import { MDXProvider } from '@mdx-js/react'
import dayjs from 'dayjs'
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
dayjs.extend(isSameOrBefore)
import cssReset from './components/cssReset'
import DefaultLayout from './layouts/DefaultLayout'
import CodeBlock from './components/CodeBlock/CodeBlock'
Expand All @@ -44,18 +43,19 @@ export default function prerender(manifest, mode) {
})

/*
2. Create a collection of blog posts (if you're not building a blog you can ignore this step!)
A blog must distinguish "posts" from other non-post pages, like your homepage.
2. Gather collections based on the meta.collection exported by each page.
We'll pass these collections to each page in step 4 below (so you can do things like list your blog posts).
*/
const collections = gatherCollections(pages)
const pageProps = { collections }

By default we look for a `meta` export like this: { collection: 'posts', date: '2020-11-30' }
but you could determine "posts" however you like (e.g. they all live in a `posts` directory, etc)
/*
3. Build a JSON Feed for the feedCollection specified package.json.
*/
const posts = collectPosts(pages)
const pageProps = { posts }
buildJSONFeedFile(pageProps)

/*
3. Build a HTML file for each page
4. Build a HTML file for each page
*/
pages.forEach(({ PageComponent, meta, urlPath }) => {
buildPageFile({
Expand All @@ -69,10 +69,31 @@ export default function prerender(manifest, mode) {
})
}

function collectPosts(pages) {
return pages
.filter((page) => page.meta && page.meta.collection === 'posts')
.sort((a, b) => new Date(b.meta.date) - new Date(a.meta.date))
/*
That's the gist of your prerender function!
Read on to understand (or change!) some of the details involved in each step.
*/

function gatherCollections(pages) {
const collections = pages.reduce((acc, page) => {
if (page.meta.collection) {
if (!acc[page.meta.collection]) acc[page.meta.collection] = []
acc[page.meta.collection].push(page)
}
return acc
}, {})

Object.keys(collections).forEach((collection) => {
collections[collection].sort((a, b) => {
if (a.meta.date && b.meta.date) {
return dayjs(a.meta.date).isSameOrBefore(b.meta.date) ? 1 : -1
} else {
return 0
}
})
})

return collections
}

function buildPageFile({ PageComponent, meta, urlPath, manifest, mode, pageProps = {} }) {
Expand All @@ -82,7 +103,9 @@ function buildPageFile({ PageComponent, meta, urlPath, manifest, mode, pageProps
})
cssReset(felaRenderer)

// 2. Render the page body HTML
// 2. Render the page body HTML, wrapped in provider components that provide:
// - Fela renderer
// - Custom MDX component to add syntax highlighting to fenced code blocks
if (!meta) throw new Error(`Page ${urlPath} does not export a meta object`)
const Layout = meta.Layout || DefaultLayout
const bodyHTML = ReactDOMServer.renderToString(
Expand Down Expand Up @@ -121,23 +144,23 @@ function buildPageFile({ PageComponent, meta, urlPath, manifest, mode, pageProps
})
}

// Get clean URLs by naming all HTML files `index.html` in a folder with the same name as the source file.
// (except source files called `index` - they can be output in place)
function cleanURLPathForPage(sourceFile) {
// Get clean URLs by naming all HTML files `index.html` in a folder with the same name as the source file.
// (except source files called `index` - they can be output in place)
return path.join('/', sourceFile.dir, sourceFile.name === 'index' ? '' : sourceFile.name)
}

function buildJSONFeedFile(pageProps) {
const { siteURL, feedTitle } = packageJSON.tropical
const { posts } = pageProps
const { siteURL, feedTitle, feedCollection } = packageJSON.tropical
const items = pageProps.collections[feedCollection]

// A minimal JSON Feed (see https://jsonfeed.org/version/1)
const feed = {
version: 'https://jsonfeed.org/version/1',
title: feedTitle,
home_page_url: siteURL,
feed_url: `${siteURL}/feed.json`,
items: posts.map(({ PageComponent, urlPath, meta }) => ({
items: items.map(({ PageComponent, urlPath, meta }) => ({
id: urlPath,
url: `${siteURL}${urlPath}`,
title: meta.title,
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"babel-loader": "^8.0.6",
"chalk": "^4.1.0",
"concurrently": "^5.1.0",
"dayjs": "^1.8.31",
"fela": "^11.1.2",
"fela-dom": "^11.1.2",
"file-loader": "^6.0.0",
Expand All @@ -33,7 +34,8 @@
"webpack-manifest-plugin": "^2.2.0"
},
"tropical": {
"feedCollection": "posts",
"feedTitle": "Blog Posts on another Tropical site",
"siteURL": ""
"siteURL": "https://example.org"
}
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3814,6 +3814,11 @@ date-fns@^2.0.1:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.14.0.tgz#359a87a265bb34ef2e38f93ecf63ac453f9bc7ba"
integrity sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw==

dayjs@^1.8.31:
version "1.8.31"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.31.tgz#0cd1114c2539dd5ad9428be0c38df6d4bb40b9d3"
integrity sha512-mPh1mslned+5PuIuiUfbw4CikHk6AEAf2Baxih+wP5fssv+wmlVhvgZ7mq+BhLt7Sr/Hc8leWDiwe6YnrpNt3g==

[email protected], debug@^2.2.0, debug@^2.3.3, debug@^2.6.0:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
Expand Down