Skip to content

Instantly share code, notes, and snippets.

@maxboeck
Created August 28, 2024 07:33
Show Gist options
  • Save maxboeck/ff82bcfa5aebec779887067d20b6f856 to your computer and use it in GitHub Desktop.
Save maxboeck/ff82bcfa5aebec779887067d20b6f856 to your computer and use it in GitHub Desktop.
import dotenv from 'dotenv'
import Eleventy from '@11ty/eleventy'
import express from 'express'
import morgan from 'morgan'
import memoize from 'lodash/memoize.js'
import path from 'path'
import fs from 'fs'
const ENV = process.env.NODE_ENV || 'development'
const PORT = process.env.PORT || 3001
const FRONTEND_DIR = process.cwd()
const STUDIO_DIR = path.join(FRONTEND_DIR, '../studio')
const BUILD_DIR = path.join(FRONTEND_DIR, 'dist')
const ASSETS_DIR = path.join(BUILD_DIR, 'assets')
const PREVIEW_DIR = path.join(BUILD_DIR, '.preview')
dotenv.config({ path: path.join(STUDIO_DIR, '.env') })
if (!fs.existsSync(BUILD_DIR)) {
console.error('Build directory does not exist. Did you run npm run build?')
}
const getURLMap = memoize(() => {
const urlMapPath = path.join(PREVIEW_DIR, 'urls.json')
if (!fs.existsSync(urlMapPath)) {
console.error(`URL input map file does not exist at ${urlMapPath}`)
return {}
}
return JSON.parse(fs.readFileSync(urlMapPath, { encoding: 'utf8' }))
})
function mapURLtoInputPath(url) {
const map = getURLMap()
if (map[url]) {
const inputPath = path.join(FRONTEND_DIR, map[url].inputPath)
if (fs.existsSync(inputPath)) {
return inputPath
}
}
return null
}
async function buildPreview(url, query) {
try {
const inputPath = mapURLtoInputPath(url)
const elev = new Eleventy(inputPath, null, {
singleTemplateScope: true,
inputDir: FRONTEND_DIR,
config: function (eleventyConfig) {
eleventyConfig.addGlobalData('preview', { url, query })
}
})
const outputJSON = await elev.toJSON()
let output = null
if (Array.isArray(outputJSON)) {
output = outputJSON.find((entry) => {
return entry.url === url
})
}
return output
} catch (err) {
console.error(err)
}
}
// custom cache resolver
const previewCacheResolver = (url, query) => {
let rev = query && query.rev ? query.rev : 0
return `${url}_${rev}`
}
// memoize preview build function,
// so calls to the same URL and revision get cached
// and don't trigger a rebuild every time
const Previewer = memoize(buildPreview, previewCacheResolver)
const app = express()
// Middleware for logging HTTP requests
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'))
// Serve static assets during development. In production, nginx does this.
if (ENV !== 'production') {
app.use('/assets', express.static(ASSETS_DIR))
}
// Endpoint that triggers the build and serves files
app.get('*', async (req, res, next) => {
try {
const { path: url, query } = req
let output
if (process.env.SANITY_STUDIO_PREVIEW_TOKEN) {
if (query.token !== process.env.SANITY_STUDIO_PREVIEW_TOKEN) {
res.status(403).send(`forbidden, no preview token provided`)
}
}
if (mapURLtoInputPath(url)) {
output = await Previewer(url, query)
if (!output) {
res.status(404).send(`no preview output found for URL: ${url}`)
} else {
res.send(output.content)
}
} else {
res.status(404).send(`can't resolve URL to input file: ${url}`)
}
} catch (err) {
return next(err)
}
})
app.listen(PORT, () => {
console.log(`Preview Server is running on port ${PORT}`)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment