Created
August 28, 2024 07:33
-
-
Save maxboeck/ff82bcfa5aebec779887067d20b6f856 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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