From c6e1a0b6fb9314186a46cf1352a8685e8aa5fe8d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Mar 2023 20:15:37 -0300 Subject: data steps: experimental live JS reload infrastructure --- src/content/dependencies/index.js | 108 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/content/dependencies/index.js (limited to 'src/content/dependencies/index.js') diff --git a/src/content/dependencies/index.js b/src/content/dependencies/index.js new file mode 100644 index 00000000..5cd116d4 --- /dev/null +++ b/src/content/dependencies/index.js @@ -0,0 +1,108 @@ +import chokidar from 'chokidar'; +import EventEmitter from 'events'; +import * as path from 'path'; +import {fileURLToPath} from 'url'; + +import contentFunction from '../../content-function.js'; +import {color, logWarn} from '../../util/cli.js'; +import {annotateFunction} from '../../util/sugar.js'; + +export function watchContentDependencies() { + const events = new EventEmitter(); + const contentDependencies = {}; + + Object.assign(events, { + contentDependencies, + }); + + // Watch adjacent files + const metaPath = fileURLToPath(import.meta.url); + const metaDirname = path.dirname(metaPath); + const watcher = chokidar.watch(metaDirname); + + watcher.on('all', (event, filePath) => { + if (!['add', 'change'].includes(event)) return; + if (filePath === metaPath) return; + handlePathUpdated(filePath); + }); + + watcher.on('unlink', (filePath) => { + if (filePath === metaPath) { + console.error(`Yeowzers content dependencies just got nuked.`); + return; + } + handlePathRemoved(filePath); + }) + + return events; + + function getFunctionName(filePath) { + const shortPath = path.basename(filePath); + const functionName = shortPath.slice(0, -path.extname(shortPath).length); + return functionName; + } + + async function handlePathRemoved(filePath) { + const functionName = getFunctionName(filePath); + delete contentDependencies[functionName]; + } + + async function handlePathUpdated(filePath) { + const functionName = getFunctionName(filePath); + let error = null; + + main: { + let spec; + try { + spec = (await import(`${filePath}?${Date.now()}`)).default; + } catch (caughtError) { + error = caughtError; + error.message = `Error importing: ${error.message}`; + break main; + } + + try { + if (typeof spec.data === 'function') { + annotateFunction(spec.data, {name: functionName, description: 'data'}); + } + + if (typeof spec.generate === 'function') { + annotateFunction(spec.generate, {name: functionName}); + } + } catch (caughtError) { + error = caughtError; + error.message = `Error annotating functions: ${error.message}`; + break main; + } + + let fn; + try { + fn = contentFunction(spec); + } catch (caughtError) { + error = caughtError; + error.message = `Error loading spec: ${error.message}`; + break main; + } + + contentDependencies[functionName] = fn; + } + + if (!error) { + return true; + } + + if (contentDependencies[functionName]) { + logWarn`Failed to import ${functionName} - using existing version`; + } else { + logWarn`Failed to import ${functionName} - no prior version loaded`; + } + + if (typeof error === 'string') { + console.error(color.yellow(error)); + } else { + console.error(error); + } + + return false; + } +} -- cgit 1.3.0-6-gf8a5