From 7c9abacf956c0e135c1094e38087e018dd572965 Mon Sep 17 00:00:00 2001 From: Leonardo Bishop Date: Mon, 28 Aug 2023 22:02:57 +0100 Subject: Rename files to more descriptive names --- app/builder/build.ts | 88 ------------------------------ app/builder/buildProject.ts | 88 ++++++++++++++++++++++++++++++ app/builder/pageDirectory.ts | 117 ++++++++++++++++++++++++++++++++++++++++ app/builder/pages.ts | 117 ---------------------------------------- app/builder/render.ts | 26 --------- app/builder/renderPage.ts | 26 +++++++++ app/index.ts | 2 +- app/webserver/fileWatcher.ts | 125 +++++++++++++++++++++++++++++++++++++++++++ app/webserver/watcher.ts | 125 ------------------------------------------- app/webserver/webserver.ts | 4 +- 10 files changed, 359 insertions(+), 359 deletions(-) delete mode 100644 app/builder/build.ts create mode 100644 app/builder/buildProject.ts create mode 100644 app/builder/pageDirectory.ts delete mode 100644 app/builder/pages.ts delete mode 100644 app/builder/render.ts create mode 100644 app/builder/renderPage.ts create mode 100644 app/webserver/fileWatcher.ts delete mode 100644 app/webserver/watcher.ts diff --git a/app/builder/build.ts b/app/builder/build.ts deleted file mode 100644 index 6807b89..0000000 --- a/app/builder/build.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { render } from './render.js'; -import { Page, PageDirectory } from './pages.js'; -import fs from 'fs'; -import path from 'path'; -import { logger } from '../logger.js'; - -export async function buildPages(): Promise<{ success: boolean, errors: number, pageDirectory: PageDirectory}> { - // Recreate output directory - if (process.env.SKIP_OUTPUT_DIR_CREATION !== 'true') { - try { - if (fs.existsSync(process.env.OUTPUT_DIR)) { - fs.rmSync(process.env.OUTPUT_DIR, { recursive: true }); - } - fs.mkdirSync(process.env.OUTPUT_DIR); - } catch (e) { - logger.error(`Failed to create output directory: ${e.message}`); - return { success: false, errors: 0, pageDirectory: null }; - } - } - - - // Load pages - logger.info(`Reading pages from disk...`); - const pageDirectory = new PageDirectory(process.env.PAGES_DIR); - await pageDirectory.init(); - - let pagesCount = Object.keys(pageDirectory.getPages()).length; - logger.info(`Found ${pagesCount} pages.`); - - - // Render pages - logger.info(`Rendering pages...`); - let pagesRendered = 0; - let pagesFailed = 0; - for (const page of pageDirectory.getPages()) { - if (await renderPage(page, pageDirectory)) { - pagesRendered++; - } else { - pagesFailed++; - } - } - - logger.info(`Rendered ${pagesRendered} of ${pagesCount} pages.`); - - - // Copy static files - logger.info(`Copying static files...`); - try { - fs.cpSync(`${process.env.STATIC_DIR}`, `${process.env.OUTPUT_DIR}/static`, { recursive: true }); - logger.info(`Done.`); - } catch (e) { - logger.error(`Failed to copy static files: ${e.message}`); - } - - return { success: pagesFailed == 0, errors: pagesFailed, pageDirectory: pageDirectory}; -} - -async function renderPage(page: Page, pageDirectory: PageDirectory): Promise { - let html; - try { - html = await render(page, pageDirectory); - } catch (e) { - logger.error(`Failed to render page ${page.originalPath}: ${e.message}`); - return false; - } - - try { - const file = page.buildPath; - const dir = path.dirname(file); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - fs.writeFileSync(file, html); - } catch (e) { - logger.error(`Failed to write page ${page.buildPath}: ${e.message}`); - return false; - } - return true; -} - -export async function rebuildSinglePage(path: string, pageDirectory: PageDirectory): Promise { - const page = await pageDirectory.loadPage(path); - if (!page) { - return false; - } - - return await renderPage(page, pageDirectory); -} diff --git a/app/builder/buildProject.ts b/app/builder/buildProject.ts new file mode 100644 index 0000000..71fdf73 --- /dev/null +++ b/app/builder/buildProject.ts @@ -0,0 +1,88 @@ +import { render } from './renderPage.js'; +import { Page, PageDirectory } from './pageDirectory.js'; +import fs from 'fs'; +import path from 'path'; +import { logger } from '../logger.js'; + +export async function buildPages(): Promise<{ success: boolean, errors: number, pageDirectory: PageDirectory}> { + // Recreate output directory + if (process.env.SKIP_OUTPUT_DIR_CREATION !== 'true') { + try { + if (fs.existsSync(process.env.OUTPUT_DIR)) { + fs.rmSync(process.env.OUTPUT_DIR, { recursive: true }); + } + fs.mkdirSync(process.env.OUTPUT_DIR); + } catch (e) { + logger.error(`Failed to create output directory: ${e.message}`); + return { success: false, errors: 0, pageDirectory: null }; + } + } + + + // Load pages + logger.info(`Reading pages from disk...`); + const pageDirectory = new PageDirectory(process.env.PAGES_DIR); + await pageDirectory.init(); + + let pagesCount = Object.keys(pageDirectory.getPages()).length; + logger.info(`Found ${pagesCount} pages.`); + + + // Render pages + logger.info(`Rendering pages...`); + let pagesRendered = 0; + let pagesFailed = 0; + for (const page of pageDirectory.getPages()) { + if (await renderPage(page, pageDirectory)) { + pagesRendered++; + } else { + pagesFailed++; + } + } + + logger.info(`Rendered ${pagesRendered} of ${pagesCount} pages.`); + + + // Copy static files + logger.info(`Copying static files...`); + try { + fs.cpSync(`${process.env.STATIC_DIR}`, `${process.env.OUTPUT_DIR}/static`, { recursive: true }); + logger.info(`Done.`); + } catch (e) { + logger.error(`Failed to copy static files: ${e.message}`); + } + + return { success: pagesFailed == 0, errors: pagesFailed, pageDirectory: pageDirectory}; +} + +async function renderPage(page: Page, pageDirectory: PageDirectory): Promise { + let html; + try { + html = await render(page, pageDirectory); + } catch (e) { + logger.error(`Failed to render page ${page.originalPath}: ${e.message}`); + return false; + } + + try { + const file = page.buildPath; + const dir = path.dirname(file); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(file, html); + } catch (e) { + logger.error(`Failed to write page ${page.buildPath}: ${e.message}`); + return false; + } + return true; +} + +export async function rebuildSinglePage(path: string, pageDirectory: PageDirectory): Promise { + const page = await pageDirectory.loadPage(path); + if (!page) { + return false; + } + + return await renderPage(page, pageDirectory); +} diff --git a/app/builder/pageDirectory.ts b/app/builder/pageDirectory.ts new file mode 100644 index 0000000..1aed94e --- /dev/null +++ b/app/builder/pageDirectory.ts @@ -0,0 +1,117 @@ +import { readFileSync } from 'fs'; +import glob from 'glob'; +import { logger } from '../logger.js' +import { marked } from 'marked'; +import { gfmHeadingId } from 'marked-gfm-heading-id'; +import matter from 'gray-matter'; + +marked.use(gfmHeadingId()); + +export async function parsePage(page: Page) { + try { + const frontmatter = matter(page.raw); + const config = frontmatter.data; + const html = marked.parse(frontmatter.content, { mangle: false }); + + page.html = html; + page.config = config; + page.view = config.view || 'index'; + page.buildTime = Date.now(); + } catch (e) { + logger.error(`Failed to parse page ${page.originalPath}: ${e.message}`); + } +} + +function loadRaw(path: string): string { + return readFileSync(`${path}`, 'utf-8'); +} + +export class PageDirectory { + private pagesPath: string; + + private pages: Record = {}; + private lastFullBuild: number; + + constructor(pagesPath: string) { + this.pagesPath = pagesPath; + + for (const page in this.pages) { + delete this.pages[page]; + } + } + + public init = async (): Promise => { + const localPages = glob.sync(`**/*.{md,html}`, { cwd: this.pagesPath }) + for (const page in localPages) { + await this.loadPage(localPages[page]); + } + this.lastFullBuild = Date.now(); + } + + public loadPage = async (page: string): Promise => { + let route = `/${page.replace(/\.[^.]*$/,'')}`; + let name = /[^/]*$/.exec(route)[0]; + let originalPath = page; + let fullPath = `${this.pagesPath}/${page}` + let buildPath = `${process.env.OUTPUT_DIR}/${route}.html` + let view = `${route}` + let raw: string; + try { + raw = loadRaw(fullPath); + } catch (e) { + logger.error(`Failed to read page ${originalPath}: ${e.message}`); + return undefined; + } + + this.pages[route] = { + route: route, + name: name, + originalPath: originalPath, + fullPath: fullPath, + buildPath: buildPath, + view: view, + raw: raw, + buildTime: 0, + config: {} + } + + await parsePage(this.pages[route]); + + return this.pages[route]; + } + + public removePage = (page: string): void => { + let route = page.replace(/\.[^.]*$/,'') + delete this.pages[route]; + } + + public get(name: string): Page { + const page = this.pages[name]; + if (!page) { + return undefined; + } + + return page; + } + + public getPages(): Page[] { + return Object.values(this.pages); + } + + public getPagesBeginningWith(prefix: string): Page[] { + return Object.values(this.pages).filter(page => page.route.startsWith(prefix)); + } +} + +export type Page = { + html?: string; + raw?: string; + route: string; + name: string; + originalPath: string; + fullPath: string; + buildPath: string; + view: string; + buildTime: number; + config: any; +}; diff --git a/app/builder/pages.ts b/app/builder/pages.ts deleted file mode 100644 index 1aed94e..0000000 --- a/app/builder/pages.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { readFileSync } from 'fs'; -import glob from 'glob'; -import { logger } from '../logger.js' -import { marked } from 'marked'; -import { gfmHeadingId } from 'marked-gfm-heading-id'; -import matter from 'gray-matter'; - -marked.use(gfmHeadingId()); - -export async function parsePage(page: Page) { - try { - const frontmatter = matter(page.raw); - const config = frontmatter.data; - const html = marked.parse(frontmatter.content, { mangle: false }); - - page.html = html; - page.config = config; - page.view = config.view || 'index'; - page.buildTime = Date.now(); - } catch (e) { - logger.error(`Failed to parse page ${page.originalPath}: ${e.message}`); - } -} - -function loadRaw(path: string): string { - return readFileSync(`${path}`, 'utf-8'); -} - -export class PageDirectory { - private pagesPath: string; - - private pages: Record = {}; - private lastFullBuild: number; - - constructor(pagesPath: string) { - this.pagesPath = pagesPath; - - for (const page in this.pages) { - delete this.pages[page]; - } - } - - public init = async (): Promise => { - const localPages = glob.sync(`**/*.{md,html}`, { cwd: this.pagesPath }) - for (const page in localPages) { - await this.loadPage(localPages[page]); - } - this.lastFullBuild = Date.now(); - } - - public loadPage = async (page: string): Promise => { - let route = `/${page.replace(/\.[^.]*$/,'')}`; - let name = /[^/]*$/.exec(route)[0]; - let originalPath = page; - let fullPath = `${this.pagesPath}/${page}` - let buildPath = `${process.env.OUTPUT_DIR}/${route}.html` - let view = `${route}` - let raw: string; - try { - raw = loadRaw(fullPath); - } catch (e) { - logger.error(`Failed to read page ${originalPath}: ${e.message}`); - return undefined; - } - - this.pages[route] = { - route: route, - name: name, - originalPath: originalPath, - fullPath: fullPath, - buildPath: buildPath, - view: view, - raw: raw, - buildTime: 0, - config: {} - } - - await parsePage(this.pages[route]); - - return this.pages[route]; - } - - public removePage = (page: string): void => { - let route = page.replace(/\.[^.]*$/,'') - delete this.pages[route]; - } - - public get(name: string): Page { - const page = this.pages[name]; - if (!page) { - return undefined; - } - - return page; - } - - public getPages(): Page[] { - return Object.values(this.pages); - } - - public getPagesBeginningWith(prefix: string): Page[] { - return Object.values(this.pages).filter(page => page.route.startsWith(prefix)); - } -} - -export type Page = { - html?: string; - raw?: string; - route: string; - name: string; - originalPath: string; - fullPath: string; - buildPath: string; - view: string; - buildTime: number; - config: any; -}; diff --git a/app/builder/render.ts b/app/builder/render.ts deleted file mode 100644 index 5c1c125..0000000 --- a/app/builder/render.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Page, PageDirectory } from "./pages"; -import ejs from 'ejs'; -import path from 'path'; -import buildInfo from "../config/info.js"; -import htmlMinify from 'html-minifier-terser'; - -export async function render(page: Page, pageDirectory: PageDirectory): Promise { - const options = { - page: page, - site: { - pages: pageDirectory, - }, - build: buildInfo, - }; - const html = await ejs.renderFile(path.join(process.env.VIEWS_DIR, `${page.view}.ejs`), options); - - const minifiedHtml = await htmlMinify.minify(html, { - collapseWhitespace: true, - removeComments: true, - continueOnParseError: true, - minifyCSS: true, - minifyJS: true, - }); - - return minifiedHtml; -} diff --git a/app/builder/renderPage.ts b/app/builder/renderPage.ts new file mode 100644 index 0000000..7dff22c --- /dev/null +++ b/app/builder/renderPage.ts @@ -0,0 +1,26 @@ +import { Page, PageDirectory } from "./pageDirectory"; +import ejs from 'ejs'; +import path from 'path'; +import buildInfo from "../config/info.js"; +import htmlMinify from 'html-minifier-terser'; + +export async function render(page: Page, pageDirectory: PageDirectory): Promise { + const options = { + page: page, + site: { + pages: pageDirectory, + }, + build: buildInfo, + }; + const html = await ejs.renderFile(path.join(process.env.VIEWS_DIR, `${page.view}.ejs`), options); + + const minifiedHtml = await htmlMinify.minify(html, { + collapseWhitespace: true, + removeComments: true, + continueOnParseError: true, + minifyCSS: true, + minifyJS: true, + }); + + return minifiedHtml; +} diff --git a/app/index.ts b/app/index.ts index 2bccdc5..e801fa3 100644 --- a/app/index.ts +++ b/app/index.ts @@ -1,6 +1,6 @@ import dotenv from 'dotenv-defaults'; import { logger } from './logger.js'; -import { buildPages } from './builder/build.js'; +import { buildPages } from './builder/buildProject.js'; import buildInfo from './config/info.js'; dotenv.config(); diff --git a/app/webserver/fileWatcher.ts b/app/webserver/fileWatcher.ts new file mode 100644 index 0000000..772ac1b --- /dev/null +++ b/app/webserver/fileWatcher.ts @@ -0,0 +1,125 @@ +import chokidar, { FSWatcher } from 'chokidar'; +import { logger } from '../logger.js'; +import { PageDirectory } from '../builder/pageDirectory.js'; +import { rebuildSinglePage } from '../builder/buildProject.js'; +import path from 'path'; +import fs from 'fs'; + +function attachPageEvents(watcher: FSWatcher, pages: PageDirectory) { + const onPageChange = async (file: string) => { + logger.info(`File ${file} has been modified, rebuilding...`); + if (await rebuildSinglePage(file, pages)) { + logger.info(`...done`); + } + logger.info(``); + } + + const onPageRemoval = (file: string) => { + logger.info(`File ${file} has been removed, deleting...`); + const page = pages.get(file.replace(/\.[^.]*$/,'')); + if (!page) { + logger.error(`Failed to find page for ${file}`); + return; + } + const joinedPath = path.join(process.env.OUTPUT_DIR, `${page.route}.html`); + try { + fs.rmSync(joinedPath) + } catch (e) { + logger.error(`Failed to remove ${joinedPath}: ${e.message}`); + } + logger.info(`...done`); + logger.info(``); + } + + watcher.on('add', onPageChange); + watcher.on('change', onPageChange); + watcher.on('unlink', onPageRemoval); +} + +function attachStaticEvents(watcher: FSWatcher) { + const onStaticChange = async (file: string) => { + logger.info(`Static file ${file} has been modified, copying...`); + const joinedPath = path.join(process.env.STATIC_DIR, file); + const joinedOutputPath = path.join(process.env.OUTPUT_DIR, 'static', file); + try { + fs.copyFileSync(joinedPath, joinedOutputPath); + logger.info(`...done`); + } catch (e) { + logger.error(`Failed to copy ${joinedPath} to ${joinedOutputPath}: ${e.message}`); + } + logger.info(``); + } + + const onStaticRemoval = (file: string) => { + logger.info(`Static file ${file} has been removed, deleting...`); + const joinedOutputPath = path.join(process.env.OUTPUT_DIR, 'static', file); + try { + fs.rmSync(joinedOutputPath) + logger.info(`...done`); + } catch (e) { + logger.error(`Failed to remove ${joinedOutputPath}: ${e.message}`); + } + logger.info(``); + } + + watcher.on('add', onStaticChange); + watcher.on('change', onStaticChange); + watcher.on('unlink', onStaticRemoval); +} + +function attachViewEvents(watcher: FSWatcher, pages: PageDirectory) { + const onViewChange = async (file: string) => { + logger.info(`View ${file} has been modified, rebuilding pages with view...`); + let pagesWithView = pages.getPages().filter(page => `${page.view}.ejs` === file); + logger.info(`Found ${pagesWithView.length} pages with view ${file}`); + for (const page of pagesWithView) { + logger.info(`Rebuilding page ${page.route}...`); + if (await rebuildSinglePage(page.originalPath, pages)) { + logger.info(`...done`); + } + } + logger.info(``); + } + + const onViewRemoval = (file: string) => { + logger.info(``); + logger.info(`View ${file} has been removed`); + logger.info(``); + } + + watcher.on('add', onViewChange); + watcher.on('change', onViewChange); + watcher.on('unlink', onViewRemoval); +} + +export const start = (pages: PageDirectory) => { + const pagesWatcher = chokidar.watch('.', { + persistent: true, + cwd: process.env.PAGES_DIR, + ignoreInitial: true, + }); + const staticWatcher = chokidar.watch('.', { + persistent: true, + cwd: process.env.STATIC_DIR, + ignoreInitial: true, + }); + const viewsWatcher = chokidar.watch('.', { + persistent: true, + cwd: process.env.VIEWS_DIR, + ignoreInitial: true, + }); + + attachPageEvents(pagesWatcher, pages); + attachStaticEvents(staticWatcher); + attachViewEvents(viewsWatcher, pages); + + const exitHandler = () => { + logger.info(`Stopping file watcher...`); + viewsWatcher.close(); + staticWatcher.close(); + pagesWatcher.close(); + } + + process.on('SIGINT', exitHandler); + process.on('SIGTERM', exitHandler); +} diff --git a/app/webserver/watcher.ts b/app/webserver/watcher.ts deleted file mode 100644 index a792473..0000000 --- a/app/webserver/watcher.ts +++ /dev/null @@ -1,125 +0,0 @@ -import chokidar, { FSWatcher } from 'chokidar'; -import { logger } from '../logger.js'; -import { PageDirectory } from '../builder/pages.js'; -import { rebuildSinglePage } from '../builder/build.js'; -import path from 'path'; -import fs from 'fs'; - -function attachPageEvents(watcher: FSWatcher, pages: PageDirectory) { - const onPageChange = async (file: string) => { - logger.info(`File ${file} has been modified, rebuilding...`); - if (await rebuildSinglePage(file, pages)) { - logger.info(`...done`); - } - logger.info(``); - } - - const onPageRemoval = (file: string) => { - logger.info(`File ${file} has been removed, deleting...`); - const page = pages.get(file.replace(/\.[^.]*$/,'')); - if (!page) { - logger.error(`Failed to find page for ${file}`); - return; - } - const joinedPath = path.join(process.env.OUTPUT_DIR, `${page.route}.html`); - try { - fs.rmSync(joinedPath) - } catch (e) { - logger.error(`Failed to remove ${joinedPath}: ${e.message}`); - } - logger.info(`...done`); - logger.info(``); - } - - watcher.on('add', onPageChange); - watcher.on('change', onPageChange); - watcher.on('unlink', onPageRemoval); -} - -function attachStaticEvents(watcher: FSWatcher) { - const onStaticChange = async (file: string) => { - logger.info(`Static file ${file} has been modified, copying...`); - const joinedPath = path.join(process.env.STATIC_DIR, file); - const joinedOutputPath = path.join(process.env.OUTPUT_DIR, 'static', file); - try { - fs.copyFileSync(joinedPath, joinedOutputPath); - logger.info(`...done`); - } catch (e) { - logger.error(`Failed to copy ${joinedPath} to ${joinedOutputPath}: ${e.message}`); - } - logger.info(``); - } - - const onStaticRemoval = (file: string) => { - logger.info(`Static file ${file} has been removed, deleting...`); - const joinedOutputPath = path.join(process.env.OUTPUT_DIR, 'static', file); - try { - fs.rmSync(joinedOutputPath) - logger.info(`...done`); - } catch (e) { - logger.error(`Failed to remove ${joinedOutputPath}: ${e.message}`); - } - logger.info(``); - } - - watcher.on('add', onStaticChange); - watcher.on('change', onStaticChange); - watcher.on('unlink', onStaticRemoval); -} - -function attachViewEvents(watcher: FSWatcher, pages: PageDirectory) { - const onViewChange = async (file: string) => { - logger.info(`View ${file} has been modified, rebuilding pages with view...`); - let pagesWithView = pages.getPages().filter(page => `${page.view}.ejs` === file); - logger.info(`Found ${pagesWithView.length} pages with view ${file}`); - for (const page of pagesWithView) { - logger.info(`Rebuilding page ${page.route}...`); - if (await rebuildSinglePage(page.originalPath, pages)) { - logger.info(`...done`); - } - } - logger.info(``); - } - - const onViewRemoval = (file: string) => { - logger.info(``); - logger.info(`View ${file} has been removed`); - logger.info(``); - } - - watcher.on('add', onViewChange); - watcher.on('change', onViewChange); - watcher.on('unlink', onViewRemoval); -} - -export const start = (pages: PageDirectory) => { - const pagesWatcher = chokidar.watch('.', { - persistent: true, - cwd: process.env.PAGES_DIR, - ignoreInitial: true, - }); - const staticWatcher = chokidar.watch('.', { - persistent: true, - cwd: process.env.STATIC_DIR, - ignoreInitial: true, - }); - const viewsWatcher = chokidar.watch('.', { - persistent: true, - cwd: process.env.VIEWS_DIR, - ignoreInitial: true, - }); - - attachPageEvents(pagesWatcher, pages); - attachStaticEvents(staticWatcher); - attachViewEvents(viewsWatcher, pages); - - const exitHandler = () => { - logger.info(`Stopping file watcher...`); - viewsWatcher.close(); - staticWatcher.close(); - pagesWatcher.close(); - } - - process.on('SIGINT', exitHandler); - process.on('SIGTERM', exitHandler); -} diff --git a/app/webserver/webserver.ts b/app/webserver/webserver.ts index 82caa06..8e99b07 100644 --- a/app/webserver/webserver.ts +++ b/app/webserver/webserver.ts @@ -1,7 +1,7 @@ import express from 'express'; import { logger } from '../logger.js'; import { AddressInfo } from 'net'; -import { PageDirectory } from '../builder/pages.js'; +import { PageDirectory } from '../builder/pageDirectory.js'; const app = express(); @@ -16,7 +16,7 @@ export const start = (pages: PageDirectory) => { logger.info('') if (process.env.WEBSERVER_AUTOREBUILD === 'true') { - import('./watcher.js').then((watcher) => { + import('./fileWatcher.js').then((watcher) => { watcher.start(pages); }); } -- cgit v1.2.3-70-g09d2